libgo: Update to weekly.2012-01-15.
From-SVN: r183539
This commit is contained in:
parent
3be18e47c3
commit
df1304ee03
192 changed files with 7870 additions and 3929 deletions
|
@ -1,4 +1,4 @@
|
|||
4a8268927758
|
||||
354b17404643
|
||||
|
||||
The first line of this file holds the Mercurial revision number of the
|
||||
last merge done from the master library sources.
|
||||
|
|
|
@ -188,7 +188,7 @@ toolexeclibgocryptoopenpgpdir = $(toolexeclibgocryptodir)/openpgp
|
|||
toolexeclibgocryptoopenpgp_DATA = \
|
||||
crypto/openpgp/armor.gox \
|
||||
crypto/openpgp/elgamal.gox \
|
||||
crypto/openpgp/error.gox \
|
||||
crypto/openpgp/errors.gox \
|
||||
crypto/openpgp/packet.gox \
|
||||
crypto/openpgp/s2k.gox
|
||||
|
||||
|
@ -235,6 +235,7 @@ toolexeclibgoexp_DATA = \
|
|||
exp/ebnf.gox \
|
||||
$(exp_inotify_gox) \
|
||||
exp/norm.gox \
|
||||
exp/proxy.gox \
|
||||
exp/spdy.gox \
|
||||
exp/sql.gox \
|
||||
exp/ssh.gox \
|
||||
|
@ -669,17 +670,25 @@ endif # !LIBGO_IS_RTEMS
|
|||
if LIBGO_IS_LINUX
|
||||
go_net_cgo_file = go/net/cgo_linux.go
|
||||
go_net_sock_file = go/net/sock_linux.go
|
||||
go_net_sockopt_file = go/net/sockopt_linux.go
|
||||
go_net_sockoptip_file = go/net/sockoptip_linux.go
|
||||
else
|
||||
if LIBGO_IS_IRIX
|
||||
go_net_cgo_file = go/net/cgo_linux.go
|
||||
go_net_sock_file = go/net/sock_linux.go
|
||||
go_net_sockopt_file = go/net/sockopt_linux.go
|
||||
go_net_sockoptip_file = go/net/sockoptip_linux.go
|
||||
else
|
||||
if LIBGO_IS_SOLARIS
|
||||
go_net_cgo_file = go/net/cgo_linux.go
|
||||
go_net_sock_file = go/net/sock_linux.go
|
||||
go_net_sockopt_file = go/net/sockopt_linux.go
|
||||
go_net_sockoptip_file = go/net/sockoptip_linux.go
|
||||
else
|
||||
go_net_cgo_file = go/net/cgo_bsd.go
|
||||
go_net_sock_file = go/net/sock_bsd.go
|
||||
go_net_sockopt_file = go/net/sockopt_bsd.go
|
||||
go_net_sockoptip_file = go/net/sockoptip_bsd.go
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
@ -728,6 +737,10 @@ go_net_files = \
|
|||
$(go_net_sendfile_file) \
|
||||
go/net/sock.go \
|
||||
$(go_net_sock_file) \
|
||||
go/net/sockopt.go \
|
||||
$(go_net_sockopt_file) \
|
||||
go/net/sockoptip.go \
|
||||
$(go_net_sockoptip_file) \
|
||||
go/net/tcpsock.go \
|
||||
go/net/tcpsock_posix.go \
|
||||
go/net/udpsock.go \
|
||||
|
@ -890,8 +903,7 @@ go_syslog_c_files = \
|
|||
go_testing_files = \
|
||||
go/testing/benchmark.go \
|
||||
go/testing/example.go \
|
||||
go/testing/testing.go \
|
||||
go/testing/wrapper.go
|
||||
go/testing/testing.go
|
||||
|
||||
go_time_files = \
|
||||
go/time/format.go \
|
||||
|
@ -1061,8 +1073,8 @@ go_crypto_openpgp_armor_files = \
|
|||
go/crypto/openpgp/armor/encode.go
|
||||
go_crypto_openpgp_elgamal_files = \
|
||||
go/crypto/openpgp/elgamal/elgamal.go
|
||||
go_crypto_openpgp_error_files = \
|
||||
go/crypto/openpgp/error/error.go
|
||||
go_crypto_openpgp_errors_files = \
|
||||
go/crypto/openpgp/errors/errors.go
|
||||
go_crypto_openpgp_packet_files = \
|
||||
go/crypto/openpgp/packet/compressed.go \
|
||||
go/crypto/openpgp/packet/encrypted_key.go \
|
||||
|
@ -1142,6 +1154,7 @@ go_encoding_pem_files = \
|
|||
go_encoding_xml_files = \
|
||||
go/encoding/xml/marshal.go \
|
||||
go/encoding/xml/read.go \
|
||||
go/encoding/xml/typeinfo.go \
|
||||
go/encoding/xml/xml.go
|
||||
|
||||
go_exp_ebnf_files = \
|
||||
|
@ -1157,6 +1170,11 @@ go_exp_norm_files = \
|
|||
go/exp/norm/readwriter.go \
|
||||
go/exp/norm/tables.go \
|
||||
go/exp/norm/trie.go
|
||||
go_exp_proxy_files = \
|
||||
go/exp/proxy/direct.go \
|
||||
go/exp/proxy/per_host.go \
|
||||
go/exp/proxy/proxy.go \
|
||||
go/exp/proxy/socks5.go
|
||||
go_exp_spdy_files = \
|
||||
go/exp/spdy/read.go \
|
||||
go/exp/spdy/types.go \
|
||||
|
@ -1173,7 +1191,7 @@ go_exp_ssh_files = \
|
|||
go/exp/ssh/doc.go \
|
||||
go/exp/ssh/messages.go \
|
||||
go/exp/ssh/server.go \
|
||||
go/exp/ssh/server_shell.go \
|
||||
go/exp/ssh/server_terminal.go \
|
||||
go/exp/ssh/session.go \
|
||||
go/exp/ssh/tcpip.go \
|
||||
go/exp/ssh/transport.go
|
||||
|
@ -1210,7 +1228,8 @@ go_go_doc_files = \
|
|||
go/go/doc/doc.go \
|
||||
go/go/doc/example.go \
|
||||
go/go/doc/exports.go \
|
||||
go/go/doc/filter.go
|
||||
go/go/doc/filter.go \
|
||||
go/go/doc/reader.go
|
||||
go_go_parser_files = \
|
||||
go/go/parser/interface.go \
|
||||
go/go/parser/parser.go
|
||||
|
@ -1461,8 +1480,15 @@ endif
|
|||
# Define ForkExec and Exec.
|
||||
if LIBGO_IS_RTEMS
|
||||
syscall_exec_file = go/syscall/exec_stubs.go
|
||||
syscall_exec_os_file =
|
||||
else
|
||||
if LIBGO_IS_LINUX
|
||||
syscall_exec_file = go/syscall/exec_unix.go
|
||||
syscall_exec_os_file = go/syscall/exec_linux.go
|
||||
else
|
||||
syscall_exec_file = go/syscall/exec_unix.go
|
||||
syscall_exec_os_file = go/syscall/exec_bsd.go
|
||||
endif
|
||||
endif
|
||||
|
||||
# Define Wait4.
|
||||
|
@ -1573,6 +1599,7 @@ go_base_syscall_files = \
|
|||
go/syscall/syscall.go \
|
||||
$(syscall_syscall_file) \
|
||||
$(syscall_exec_file) \
|
||||
$(syscall_exec_os_file) \
|
||||
$(syscall_wait_file) \
|
||||
$(syscall_sleep_file) \
|
||||
$(syscall_errstr_file) \
|
||||
|
@ -1720,7 +1747,7 @@ libgo_go_objs = \
|
|||
crypto/xtea.lo \
|
||||
crypto/openpgp/armor.lo \
|
||||
crypto/openpgp/elgamal.lo \
|
||||
crypto/openpgp/error.lo \
|
||||
crypto/openpgp/errors.lo \
|
||||
crypto/openpgp/packet.lo \
|
||||
crypto/openpgp/s2k.lo \
|
||||
crypto/x509/pkix.lo \
|
||||
|
@ -1743,6 +1770,7 @@ libgo_go_objs = \
|
|||
encoding/xml.lo \
|
||||
exp/ebnf.lo \
|
||||
exp/norm.lo \
|
||||
exp/proxy.lo \
|
||||
exp/spdy.lo \
|
||||
exp/sql.lo \
|
||||
exp/ssh.lo \
|
||||
|
@ -2578,15 +2606,15 @@ crypto/openpgp/elgamal/check: $(CHECK_DEPS)
|
|||
@$(CHECK)
|
||||
.PHONY: crypto/openpgp/elgamal/check
|
||||
|
||||
@go_include@ crypto/openpgp/error.lo.dep
|
||||
crypto/openpgp/error.lo.dep: $(go_crypto_openpgp_error_files)
|
||||
@go_include@ crypto/openpgp/errors.lo.dep
|
||||
crypto/openpgp/errors.lo.dep: $(go_crypto_openpgp_errors_files)
|
||||
$(BUILDDEPS)
|
||||
crypto/openpgp/error.lo: $(go_crypto_openpgp_error_files)
|
||||
crypto/openpgp/errors.lo: $(go_crypto_openpgp_errors_files)
|
||||
$(BUILDPACKAGE)
|
||||
crypto/openpgp/error/check: $(CHECK_DEPS)
|
||||
@$(MKDIR_P) crypto/openpgp/error
|
||||
crypto/openpgp/errors/check: $(CHECK_DEPS)
|
||||
@$(MKDIR_P) crypto/openpgp/errors
|
||||
@$(CHECK)
|
||||
.PHONY: crypto/openpgp/error/check
|
||||
.PHONY: crypto/openpgp/errors/check
|
||||
|
||||
@go_include@ crypto/openpgp/packet.lo.dep
|
||||
crypto/openpgp/packet.lo.dep: $(go_crypto_openpgp_packet_files)
|
||||
|
@ -2808,6 +2836,16 @@ exp/norm/check: $(CHECK_DEPS)
|
|||
@$(CHECK)
|
||||
.PHONY: exp/norm/check
|
||||
|
||||
@go_include@ exp/proxy.lo.dep
|
||||
exp/proxy.lo.dep: $(go_exp_proxy_files)
|
||||
$(BUILDDEPS)
|
||||
exp/proxy.lo: $(go_exp_proxy_files)
|
||||
$(BUILDPACKAGE)
|
||||
exp/proxy/check: $(CHECK_DEPS)
|
||||
@$(MKDIR_P) exp/proxy
|
||||
@$(CHECK)
|
||||
.PHONY: exp/proxy/check
|
||||
|
||||
@go_include@ exp/spdy.lo.dep
|
||||
exp/spdy.lo.dep: $(go_exp_spdy_files)
|
||||
$(BUILDDEPS)
|
||||
|
@ -3622,7 +3660,7 @@ crypto/openpgp/armor.gox: crypto/openpgp/armor.lo
|
|||
$(BUILDGOX)
|
||||
crypto/openpgp/elgamal.gox: crypto/openpgp/elgamal.lo
|
||||
$(BUILDGOX)
|
||||
crypto/openpgp/error.gox: crypto/openpgp/error.lo
|
||||
crypto/openpgp/errors.gox: crypto/openpgp/errors.lo
|
||||
$(BUILDGOX)
|
||||
crypto/openpgp/packet.gox: crypto/openpgp/packet.lo
|
||||
$(BUILDGOX)
|
||||
|
@ -3674,6 +3712,8 @@ exp/inotify.gox: exp/inotify.lo
|
|||
$(BUILDGOX)
|
||||
exp/norm.gox: exp/norm.lo
|
||||
$(BUILDGOX)
|
||||
exp/proxy.gox: exp/proxy.lo
|
||||
$(BUILDGOX)
|
||||
exp/spdy.gox: exp/spdy.lo
|
||||
$(BUILDGOX)
|
||||
exp/sql.gox: exp/sql.lo
|
||||
|
@ -3920,6 +3960,7 @@ TEST_PACKAGES = \
|
|||
exp/ebnf/check \
|
||||
$(exp_inotify_check) \
|
||||
exp/norm/check \
|
||||
exp/proxy/check \
|
||||
exp/spdy/check \
|
||||
exp/sql/check \
|
||||
exp/ssh/check \
|
||||
|
|
|
@ -153,33 +153,34 @@ am__DEPENDENCIES_2 = bufio/bufio.lo bytes/bytes.lo bytes/index.lo \
|
|||
crypto/sha256.lo crypto/sha512.lo crypto/subtle.lo \
|
||||
crypto/tls.lo crypto/twofish.lo crypto/x509.lo crypto/xtea.lo \
|
||||
crypto/openpgp/armor.lo crypto/openpgp/elgamal.lo \
|
||||
crypto/openpgp/error.lo crypto/openpgp/packet.lo \
|
||||
crypto/openpgp/errors.lo crypto/openpgp/packet.lo \
|
||||
crypto/openpgp/s2k.lo crypto/x509/pkix.lo debug/dwarf.lo \
|
||||
debug/elf.lo debug/gosym.lo debug/macho.lo debug/pe.lo \
|
||||
encoding/ascii85.lo encoding/asn1.lo encoding/base32.lo \
|
||||
encoding/base64.lo encoding/binary.lo encoding/csv.lo \
|
||||
encoding/git85.lo encoding/gob.lo encoding/hex.lo \
|
||||
encoding/json.lo encoding/pem.lo encoding/xml.lo exp/ebnf.lo \
|
||||
exp/norm.lo exp/spdy.lo exp/sql.lo exp/ssh.lo exp/terminal.lo \
|
||||
exp/types.lo exp/sql/driver.lo html/template.lo go/ast.lo \
|
||||
go/build.lo go/doc.lo go/parser.lo go/printer.lo go/scanner.lo \
|
||||
go/token.lo hash/adler32.lo hash/crc32.lo hash/crc64.lo \
|
||||
hash/fnv.lo net/http/cgi.lo net/http/fcgi.lo \
|
||||
net/http/httptest.lo net/http/httputil.lo net/http/pprof.lo \
|
||||
image/bmp.lo image/color.lo image/draw.lo image/gif.lo \
|
||||
image/jpeg.lo image/png.lo image/tiff.lo index/suffixarray.lo \
|
||||
io/ioutil.lo log/syslog.lo log/syslog/syslog_c.lo math/big.lo \
|
||||
math/cmplx.lo math/rand.lo mime/mime.lo mime/multipart.lo \
|
||||
net/dict.lo net/http.lo net/mail.lo net/rpc.lo net/smtp.lo \
|
||||
net/textproto.lo net/url.lo old/netchan.lo old/regexp.lo \
|
||||
old/template.lo $(am__DEPENDENCIES_1) os/user.lo os/signal.lo \
|
||||
path/filepath.lo regexp/syntax.lo net/rpc/jsonrpc.lo \
|
||||
runtime/debug.lo runtime/pprof.lo sync/atomic.lo \
|
||||
sync/atomic_c.lo syscall/syscall.lo syscall/errno.lo \
|
||||
syscall/wait.lo text/scanner.lo text/tabwriter.lo \
|
||||
text/template.lo text/template/parse.lo testing/testing.lo \
|
||||
testing/iotest.lo testing/quick.lo testing/script.lo \
|
||||
unicode/utf16.lo unicode/utf8.lo
|
||||
exp/norm.lo exp/proxy.lo exp/spdy.lo exp/sql.lo exp/ssh.lo \
|
||||
exp/terminal.lo exp/types.lo exp/sql/driver.lo \
|
||||
html/template.lo go/ast.lo go/build.lo go/doc.lo go/parser.lo \
|
||||
go/printer.lo go/scanner.lo go/token.lo hash/adler32.lo \
|
||||
hash/crc32.lo hash/crc64.lo hash/fnv.lo net/http/cgi.lo \
|
||||
net/http/fcgi.lo net/http/httptest.lo net/http/httputil.lo \
|
||||
net/http/pprof.lo image/bmp.lo image/color.lo image/draw.lo \
|
||||
image/gif.lo image/jpeg.lo image/png.lo image/tiff.lo \
|
||||
index/suffixarray.lo io/ioutil.lo log/syslog.lo \
|
||||
log/syslog/syslog_c.lo math/big.lo math/cmplx.lo math/rand.lo \
|
||||
mime/mime.lo mime/multipart.lo net/dict.lo net/http.lo \
|
||||
net/mail.lo net/rpc.lo net/smtp.lo net/textproto.lo net/url.lo \
|
||||
old/netchan.lo old/regexp.lo old/template.lo \
|
||||
$(am__DEPENDENCIES_1) os/user.lo os/signal.lo path/filepath.lo \
|
||||
regexp/syntax.lo net/rpc/jsonrpc.lo runtime/debug.lo \
|
||||
runtime/pprof.lo sync/atomic.lo sync/atomic_c.lo \
|
||||
syscall/syscall.lo syscall/errno.lo syscall/wait.lo \
|
||||
text/scanner.lo text/tabwriter.lo text/template.lo \
|
||||
text/template/parse.lo testing/testing.lo testing/iotest.lo \
|
||||
testing/quick.lo testing/script.lo unicode/utf16.lo \
|
||||
unicode/utf8.lo
|
||||
libgo_la_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
|
||||
$(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
|
||||
$(am__DEPENDENCIES_1)
|
||||
|
@ -652,7 +653,7 @@ toolexeclibgocryptoopenpgpdir = $(toolexeclibgocryptodir)/openpgp
|
|||
toolexeclibgocryptoopenpgp_DATA = \
|
||||
crypto/openpgp/armor.gox \
|
||||
crypto/openpgp/elgamal.gox \
|
||||
crypto/openpgp/error.gox \
|
||||
crypto/openpgp/errors.gox \
|
||||
crypto/openpgp/packet.gox \
|
||||
crypto/openpgp/s2k.gox
|
||||
|
||||
|
@ -692,6 +693,7 @@ toolexeclibgoexp_DATA = \
|
|||
exp/ebnf.gox \
|
||||
$(exp_inotify_gox) \
|
||||
exp/norm.gox \
|
||||
exp/proxy.gox \
|
||||
exp/spdy.gox \
|
||||
exp/sql.gox \
|
||||
exp/ssh.gox \
|
||||
|
@ -1049,6 +1051,14 @@ go_mime_files = \
|
|||
@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_TRUE@go_net_sock_file = go/net/sock_linux.go
|
||||
@LIBGO_IS_IRIX_TRUE@@LIBGO_IS_LINUX_FALSE@go_net_sock_file = go/net/sock_linux.go
|
||||
@LIBGO_IS_LINUX_TRUE@go_net_sock_file = go/net/sock_linux.go
|
||||
@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_sockopt_file = go/net/sockopt_bsd.go
|
||||
@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_TRUE@go_net_sockopt_file = go/net/sockopt_linux.go
|
||||
@LIBGO_IS_IRIX_TRUE@@LIBGO_IS_LINUX_FALSE@go_net_sockopt_file = go/net/sockopt_linux.go
|
||||
@LIBGO_IS_LINUX_TRUE@go_net_sockopt_file = go/net/sockopt_linux.go
|
||||
@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_FALSE@go_net_sockoptip_file = go/net/sockoptip_bsd.go
|
||||
@LIBGO_IS_IRIX_FALSE@@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_SOLARIS_TRUE@go_net_sockoptip_file = go/net/sockoptip_linux.go
|
||||
@LIBGO_IS_IRIX_TRUE@@LIBGO_IS_LINUX_FALSE@go_net_sockoptip_file = go/net/sockoptip_linux.go
|
||||
@LIBGO_IS_LINUX_TRUE@go_net_sockoptip_file = go/net/sockoptip_linux.go
|
||||
@LIBGO_IS_LINUX_FALSE@go_net_sendfile_file = go/net/sendfile_stub.go
|
||||
@LIBGO_IS_LINUX_TRUE@go_net_sendfile_file = go/net/sendfile_linux.go
|
||||
@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_NETBSD_FALSE@go_net_interface_file = go/net/interface_stub.go
|
||||
|
@ -1082,6 +1092,10 @@ go_net_files = \
|
|||
$(go_net_sendfile_file) \
|
||||
go/net/sock.go \
|
||||
$(go_net_sock_file) \
|
||||
go/net/sockopt.go \
|
||||
$(go_net_sockopt_file) \
|
||||
go/net/sockoptip.go \
|
||||
$(go_net_sockoptip_file) \
|
||||
go/net/tcpsock.go \
|
||||
go/net/tcpsock_posix.go \
|
||||
go/net/udpsock.go \
|
||||
|
@ -1197,8 +1211,7 @@ go_syslog_c_files = \
|
|||
go_testing_files = \
|
||||
go/testing/benchmark.go \
|
||||
go/testing/example.go \
|
||||
go/testing/testing.go \
|
||||
go/testing/wrapper.go
|
||||
go/testing/testing.go
|
||||
|
||||
go_time_files = \
|
||||
go/time/format.go \
|
||||
|
@ -1394,8 +1407,8 @@ go_crypto_openpgp_armor_files = \
|
|||
go_crypto_openpgp_elgamal_files = \
|
||||
go/crypto/openpgp/elgamal/elgamal.go
|
||||
|
||||
go_crypto_openpgp_error_files = \
|
||||
go/crypto/openpgp/error/error.go
|
||||
go_crypto_openpgp_errors_files = \
|
||||
go/crypto/openpgp/errors/errors.go
|
||||
|
||||
go_crypto_openpgp_packet_files = \
|
||||
go/crypto/openpgp/packet/compressed.go \
|
||||
|
@ -1492,6 +1505,7 @@ go_encoding_pem_files = \
|
|||
go_encoding_xml_files = \
|
||||
go/encoding/xml/marshal.go \
|
||||
go/encoding/xml/read.go \
|
||||
go/encoding/xml/typeinfo.go \
|
||||
go/encoding/xml/xml.go
|
||||
|
||||
go_exp_ebnf_files = \
|
||||
|
@ -1510,6 +1524,12 @@ go_exp_norm_files = \
|
|||
go/exp/norm/tables.go \
|
||||
go/exp/norm/trie.go
|
||||
|
||||
go_exp_proxy_files = \
|
||||
go/exp/proxy/direct.go \
|
||||
go/exp/proxy/per_host.go \
|
||||
go/exp/proxy/proxy.go \
|
||||
go/exp/proxy/socks5.go
|
||||
|
||||
go_exp_spdy_files = \
|
||||
go/exp/spdy/read.go \
|
||||
go/exp/spdy/types.go \
|
||||
|
@ -1528,7 +1548,7 @@ go_exp_ssh_files = \
|
|||
go/exp/ssh/doc.go \
|
||||
go/exp/ssh/messages.go \
|
||||
go/exp/ssh/server.go \
|
||||
go/exp/ssh/server_shell.go \
|
||||
go/exp/ssh/server_terminal.go \
|
||||
go/exp/ssh/session.go \
|
||||
go/exp/ssh/tcpip.go \
|
||||
go/exp/ssh/transport.go
|
||||
|
@ -1569,7 +1589,8 @@ go_go_doc_files = \
|
|||
go/go/doc/doc.go \
|
||||
go/go/doc/example.go \
|
||||
go/go/doc/exports.go \
|
||||
go/go/doc/filter.go
|
||||
go/go/doc/filter.go \
|
||||
go/go/doc/reader.go
|
||||
|
||||
go_go_parser_files = \
|
||||
go/go/parser/interface.go \
|
||||
|
@ -1840,10 +1861,14 @@ go_unicode_utf8_files = \
|
|||
|
||||
# Define Syscall and Syscall6.
|
||||
@LIBGO_IS_RTEMS_TRUE@syscall_syscall_file = go/syscall/syscall_stubs.go
|
||||
@LIBGO_IS_RTEMS_FALSE@syscall_exec_file = go/syscall/exec_unix.go
|
||||
@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_RTEMS_FALSE@syscall_exec_file = go/syscall/exec_unix.go
|
||||
@LIBGO_IS_LINUX_TRUE@@LIBGO_IS_RTEMS_FALSE@syscall_exec_file = go/syscall/exec_unix.go
|
||||
|
||||
# Define ForkExec and Exec.
|
||||
@LIBGO_IS_RTEMS_TRUE@syscall_exec_file = go/syscall/exec_stubs.go
|
||||
@LIBGO_IS_LINUX_FALSE@@LIBGO_IS_RTEMS_FALSE@syscall_exec_os_file = go/syscall/exec_bsd.go
|
||||
@LIBGO_IS_LINUX_TRUE@@LIBGO_IS_RTEMS_FALSE@syscall_exec_os_file = go/syscall/exec_linux.go
|
||||
@LIBGO_IS_RTEMS_TRUE@syscall_exec_os_file =
|
||||
@HAVE_WAIT4_FALSE@@LIBGO_IS_RTEMS_FALSE@syscall_wait_file = go/syscall/libcall_waitpid.go
|
||||
@HAVE_WAIT4_TRUE@@LIBGO_IS_RTEMS_FALSE@syscall_wait_file = go/syscall/libcall_wait4.go
|
||||
|
||||
|
@ -1901,6 +1926,7 @@ go_base_syscall_files = \
|
|||
go/syscall/syscall.go \
|
||||
$(syscall_syscall_file) \
|
||||
$(syscall_exec_file) \
|
||||
$(syscall_exec_os_file) \
|
||||
$(syscall_wait_file) \
|
||||
$(syscall_sleep_file) \
|
||||
$(syscall_errstr_file) \
|
||||
|
@ -1995,7 +2021,7 @@ libgo_go_objs = \
|
|||
crypto/xtea.lo \
|
||||
crypto/openpgp/armor.lo \
|
||||
crypto/openpgp/elgamal.lo \
|
||||
crypto/openpgp/error.lo \
|
||||
crypto/openpgp/errors.lo \
|
||||
crypto/openpgp/packet.lo \
|
||||
crypto/openpgp/s2k.lo \
|
||||
crypto/x509/pkix.lo \
|
||||
|
@ -2018,6 +2044,7 @@ libgo_go_objs = \
|
|||
encoding/xml.lo \
|
||||
exp/ebnf.lo \
|
||||
exp/norm.lo \
|
||||
exp/proxy.lo \
|
||||
exp/spdy.lo \
|
||||
exp/sql.lo \
|
||||
exp/ssh.lo \
|
||||
|
@ -2286,6 +2313,7 @@ TEST_PACKAGES = \
|
|||
exp/ebnf/check \
|
||||
$(exp_inotify_check) \
|
||||
exp/norm/check \
|
||||
exp/proxy/check \
|
||||
exp/spdy/check \
|
||||
exp/sql/check \
|
||||
exp/ssh/check \
|
||||
|
@ -5162,15 +5190,15 @@ crypto/openpgp/elgamal/check: $(CHECK_DEPS)
|
|||
@$(CHECK)
|
||||
.PHONY: crypto/openpgp/elgamal/check
|
||||
|
||||
@go_include@ crypto/openpgp/error.lo.dep
|
||||
crypto/openpgp/error.lo.dep: $(go_crypto_openpgp_error_files)
|
||||
@go_include@ crypto/openpgp/errors.lo.dep
|
||||
crypto/openpgp/errors.lo.dep: $(go_crypto_openpgp_errors_files)
|
||||
$(BUILDDEPS)
|
||||
crypto/openpgp/error.lo: $(go_crypto_openpgp_error_files)
|
||||
crypto/openpgp/errors.lo: $(go_crypto_openpgp_errors_files)
|
||||
$(BUILDPACKAGE)
|
||||
crypto/openpgp/error/check: $(CHECK_DEPS)
|
||||
@$(MKDIR_P) crypto/openpgp/error
|
||||
crypto/openpgp/errors/check: $(CHECK_DEPS)
|
||||
@$(MKDIR_P) crypto/openpgp/errors
|
||||
@$(CHECK)
|
||||
.PHONY: crypto/openpgp/error/check
|
||||
.PHONY: crypto/openpgp/errors/check
|
||||
|
||||
@go_include@ crypto/openpgp/packet.lo.dep
|
||||
crypto/openpgp/packet.lo.dep: $(go_crypto_openpgp_packet_files)
|
||||
|
@ -5392,6 +5420,16 @@ exp/norm/check: $(CHECK_DEPS)
|
|||
@$(CHECK)
|
||||
.PHONY: exp/norm/check
|
||||
|
||||
@go_include@ exp/proxy.lo.dep
|
||||
exp/proxy.lo.dep: $(go_exp_proxy_files)
|
||||
$(BUILDDEPS)
|
||||
exp/proxy.lo: $(go_exp_proxy_files)
|
||||
$(BUILDPACKAGE)
|
||||
exp/proxy/check: $(CHECK_DEPS)
|
||||
@$(MKDIR_P) exp/proxy
|
||||
@$(CHECK)
|
||||
.PHONY: exp/proxy/check
|
||||
|
||||
@go_include@ exp/spdy.lo.dep
|
||||
exp/spdy.lo.dep: $(go_exp_spdy_files)
|
||||
$(BUILDDEPS)
|
||||
|
@ -6201,7 +6239,7 @@ crypto/openpgp/armor.gox: crypto/openpgp/armor.lo
|
|||
$(BUILDGOX)
|
||||
crypto/openpgp/elgamal.gox: crypto/openpgp/elgamal.lo
|
||||
$(BUILDGOX)
|
||||
crypto/openpgp/error.gox: crypto/openpgp/error.lo
|
||||
crypto/openpgp/errors.gox: crypto/openpgp/errors.lo
|
||||
$(BUILDGOX)
|
||||
crypto/openpgp/packet.gox: crypto/openpgp/packet.lo
|
||||
$(BUILDGOX)
|
||||
|
@ -6253,6 +6291,8 @@ exp/inotify.gox: exp/inotify.lo
|
|||
$(BUILDGOX)
|
||||
exp/norm.gox: exp/norm.lo
|
||||
$(BUILDGOX)
|
||||
exp/proxy.gox: exp/proxy.lo
|
||||
$(BUILDGOX)
|
||||
exp/spdy.gox: exp/spdy.lo
|
||||
$(BUILDGOX)
|
||||
exp/sql.gox: exp/sql.lo
|
||||
|
|
|
@ -74,6 +74,9 @@
|
|||
/* Define to 1 if you have the <sys/mman.h> header file. */
|
||||
#undef HAVE_SYS_MMAN_H
|
||||
|
||||
/* Define to 1 if you have the <sys/prctl.h> header file. */
|
||||
#undef HAVE_SYS_PRCTL_H
|
||||
|
||||
/* Define to 1 if you have the <sys/ptrace.h> header file. */
|
||||
#undef HAVE_SYS_PTRACE_H
|
||||
|
||||
|
|
2
libgo/configure
vendored
2
libgo/configure
vendored
|
@ -14505,7 +14505,7 @@ no)
|
|||
;;
|
||||
esac
|
||||
|
||||
for ac_header in sys/mman.h syscall.h sys/epoll.h sys/ptrace.h sys/syscall.h sys/user.h sys/utsname.h sys/select.h sys/socket.h net/if.h
|
||||
for ac_header in sys/mman.h syscall.h sys/epoll.h sys/ptrace.h sys/syscall.h sys/user.h sys/utsname.h sys/select.h sys/socket.h net/if.h sys/prctl.h
|
||||
do :
|
||||
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
|
||||
ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
|
||||
|
|
|
@ -451,7 +451,7 @@ no)
|
|||
;;
|
||||
esac
|
||||
|
||||
AC_CHECK_HEADERS(sys/mman.h syscall.h sys/epoll.h sys/ptrace.h sys/syscall.h sys/user.h sys/utsname.h sys/select.h sys/socket.h net/if.h)
|
||||
AC_CHECK_HEADERS(sys/mman.h syscall.h sys/epoll.h sys/ptrace.h sys/syscall.h sys/user.h sys/utsname.h sys/select.h sys/socket.h net/if.h sys/prctl.h)
|
||||
|
||||
AC_CHECK_HEADERS([linux/filter.h linux/netlink.h linux/rtnetlink.h], [], [],
|
||||
[#ifdef HAVE_SYS_SOCKET_H
|
||||
|
|
|
@ -97,8 +97,7 @@ func (b *Buffer) grow(n int) int {
|
|||
func (b *Buffer) Write(p []byte) (n int, err error) {
|
||||
b.lastRead = opInvalid
|
||||
m := b.grow(len(p))
|
||||
copy(b.buf[m:], p)
|
||||
return len(p), nil
|
||||
return copy(b.buf[m:], p), nil
|
||||
}
|
||||
|
||||
// WriteString appends the contents of s to the buffer. The return
|
||||
|
@ -200,13 +199,16 @@ func (b *Buffer) WriteRune(r rune) (n int, err error) {
|
|||
|
||||
// Read reads the next len(p) bytes from the buffer or until the buffer
|
||||
// is drained. The return value n is the number of bytes read. If the
|
||||
// buffer has no data to return, err is io.EOF even if len(p) is zero;
|
||||
// buffer has no data to return, err is io.EOF (unless len(p) is zero);
|
||||
// otherwise it is nil.
|
||||
func (b *Buffer) Read(p []byte) (n int, err error) {
|
||||
b.lastRead = opInvalid
|
||||
if b.off >= len(b.buf) {
|
||||
// Buffer is empty, reset to recover space.
|
||||
b.Truncate(0)
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(p, b.buf[b.off:])
|
||||
|
|
|
@ -373,3 +373,16 @@ func TestReadBytes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Was a bug: used to give EOF reading empty slice at EOF.
|
||||
func TestReadEmptyAtEOF(t *testing.T) {
|
||||
b := new(Buffer)
|
||||
slice := make([]byte, 0)
|
||||
n, err := b.Read(slice)
|
||||
if err != nil {
|
||||
t.Errorf("read error: %v", err)
|
||||
}
|
||||
if n != 0 {
|
||||
t.Errorf("wrong count; got %d want 0", n)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ package armor
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ type Block struct {
|
|||
oReader openpgpReader
|
||||
}
|
||||
|
||||
var ArmorCorrupt error = error_.StructuralError("armor invalid")
|
||||
var ArmorCorrupt error = errors.StructuralError("armor invalid")
|
||||
|
||||
const crc24Init = 0xb704ce
|
||||
const crc24Poly = 0x1864cfb
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package error contains common error types for the OpenPGP packages.
|
||||
package error
|
||||
// Package errors contains common error types for the OpenPGP packages.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"strconv"
|
|
@ -7,8 +7,9 @@ package openpgp
|
|||
import (
|
||||
"crypto"
|
||||
"crypto/openpgp/armor"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"crypto/openpgp/packet"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"io"
|
||||
"time"
|
||||
|
@ -181,13 +182,13 @@ func (el EntityList) DecryptionKeys() (keys []Key) {
|
|||
func ReadArmoredKeyRing(r io.Reader) (EntityList, error) {
|
||||
block, err := armor.Decode(r)
|
||||
if err == io.EOF {
|
||||
return nil, error_.InvalidArgumentError("no armored data found")
|
||||
return nil, errors.InvalidArgumentError("no armored data found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if block.Type != PublicKeyType && block.Type != PrivateKeyType {
|
||||
return nil, error_.InvalidArgumentError("expected public or private key block, got: " + block.Type)
|
||||
return nil, errors.InvalidArgumentError("expected public or private key block, got: " + block.Type)
|
||||
}
|
||||
|
||||
return ReadKeyRing(block.Body)
|
||||
|
@ -203,7 +204,7 @@ func ReadKeyRing(r io.Reader) (el EntityList, err error) {
|
|||
var e *Entity
|
||||
e, err = readEntity(packets)
|
||||
if err != nil {
|
||||
if _, ok := err.(error_.UnsupportedError); ok {
|
||||
if _, ok := err.(errors.UnsupportedError); ok {
|
||||
lastUnsupportedError = err
|
||||
err = readToNextPublicKey(packets)
|
||||
}
|
||||
|
@ -235,7 +236,7 @@ func readToNextPublicKey(packets *packet.Reader) (err error) {
|
|||
if err == io.EOF {
|
||||
return
|
||||
} else if err != nil {
|
||||
if _, ok := err.(error_.UnsupportedError); ok {
|
||||
if _, ok := err.(errors.UnsupportedError); ok {
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
|
@ -266,14 +267,14 @@ func readEntity(packets *packet.Reader) (*Entity, error) {
|
|||
if e.PrimaryKey, ok = p.(*packet.PublicKey); !ok {
|
||||
if e.PrivateKey, ok = p.(*packet.PrivateKey); !ok {
|
||||
packets.Unread(p)
|
||||
return nil, error_.StructuralError("first packet was not a public/private key")
|
||||
return nil, errors.StructuralError("first packet was not a public/private key")
|
||||
} else {
|
||||
e.PrimaryKey = &e.PrivateKey.PublicKey
|
||||
}
|
||||
}
|
||||
|
||||
if !e.PrimaryKey.PubKeyAlgo.CanSign() {
|
||||
return nil, error_.StructuralError("primary key cannot be used for signatures")
|
||||
return nil, errors.StructuralError("primary key cannot be used for signatures")
|
||||
}
|
||||
|
||||
var current *Identity
|
||||
|
@ -303,12 +304,12 @@ EachPacket:
|
|||
|
||||
sig, ok := p.(*packet.Signature)
|
||||
if !ok {
|
||||
return nil, error_.StructuralError("user ID packet not followed by self-signature")
|
||||
return nil, errors.StructuralError("user ID packet not followed by self-signature")
|
||||
}
|
||||
|
||||
if (sig.SigType == packet.SigTypePositiveCert || sig.SigType == packet.SigTypeGenericCert) && sig.IssuerKeyId != nil && *sig.IssuerKeyId == e.PrimaryKey.KeyId {
|
||||
if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, sig); err != nil {
|
||||
return nil, error_.StructuralError("user ID self-signature invalid: " + err.Error())
|
||||
return nil, errors.StructuralError("user ID self-signature invalid: " + err.Error())
|
||||
}
|
||||
current.SelfSignature = sig
|
||||
break
|
||||
|
@ -317,7 +318,7 @@ EachPacket:
|
|||
}
|
||||
case *packet.Signature:
|
||||
if current == nil {
|
||||
return nil, error_.StructuralError("signature packet found before user id packet")
|
||||
return nil, errors.StructuralError("signature packet found before user id packet")
|
||||
}
|
||||
current.Signatures = append(current.Signatures, pkt)
|
||||
case *packet.PrivateKey:
|
||||
|
@ -344,7 +345,7 @@ EachPacket:
|
|||
}
|
||||
|
||||
if len(e.Identities) == 0 {
|
||||
return nil, error_.StructuralError("entity without any identities")
|
||||
return nil, errors.StructuralError("entity without any identities")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
|
@ -359,19 +360,19 @@ func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *p
|
|||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err != nil {
|
||||
return error_.StructuralError("subkey signature invalid: " + err.Error())
|
||||
return errors.StructuralError("subkey signature invalid: " + err.Error())
|
||||
}
|
||||
var ok bool
|
||||
subKey.Sig, ok = p.(*packet.Signature)
|
||||
if !ok {
|
||||
return error_.StructuralError("subkey packet not followed by signature")
|
||||
return errors.StructuralError("subkey packet not followed by signature")
|
||||
}
|
||||
if subKey.Sig.SigType != packet.SigTypeSubkeyBinding {
|
||||
return error_.StructuralError("subkey signature with wrong type")
|
||||
return errors.StructuralError("subkey signature with wrong type")
|
||||
}
|
||||
err = e.PrimaryKey.VerifyKeySignature(subKey.PublicKey, subKey.Sig)
|
||||
if err != nil {
|
||||
return error_.StructuralError("subkey signature invalid: " + err.Error())
|
||||
return errors.StructuralError("subkey signature invalid: " + err.Error())
|
||||
}
|
||||
e.Subkeys = append(e.Subkeys, subKey)
|
||||
return nil
|
||||
|
@ -385,7 +386,7 @@ const defaultRSAKeyBits = 2048
|
|||
func NewEntity(rand io.Reader, currentTime time.Time, name, comment, email string) (*Entity, error) {
|
||||
uid := packet.NewUserId(name, comment, email)
|
||||
if uid == nil {
|
||||
return nil, error_.InvalidArgumentError("user id field contained invalid characters")
|
||||
return nil, errors.InvalidArgumentError("user id field contained invalid characters")
|
||||
}
|
||||
signingPriv, err := rsa.GenerateKey(rand, defaultRSAKeyBits)
|
||||
if err != nil {
|
||||
|
@ -397,8 +398,8 @@ func NewEntity(rand io.Reader, currentTime time.Time, name, comment, email strin
|
|||
}
|
||||
|
||||
e := &Entity{
|
||||
PrimaryKey: packet.NewRSAPublicKey(currentTime, &signingPriv.PublicKey, false /* not a subkey */ ),
|
||||
PrivateKey: packet.NewRSAPrivateKey(currentTime, signingPriv, false /* not a subkey */ ),
|
||||
PrimaryKey: packet.NewRSAPublicKey(currentTime, &signingPriv.PublicKey),
|
||||
PrivateKey: packet.NewRSAPrivateKey(currentTime, signingPriv),
|
||||
Identities: make(map[string]*Identity),
|
||||
}
|
||||
isPrimaryId := true
|
||||
|
@ -420,8 +421,8 @@ func NewEntity(rand io.Reader, currentTime time.Time, name, comment, email strin
|
|||
|
||||
e.Subkeys = make([]Subkey, 1)
|
||||
e.Subkeys[0] = Subkey{
|
||||
PublicKey: packet.NewRSAPublicKey(currentTime, &encryptingPriv.PublicKey, true /* is a subkey */ ),
|
||||
PrivateKey: packet.NewRSAPrivateKey(currentTime, encryptingPriv, true /* is a subkey */ ),
|
||||
PublicKey: packet.NewRSAPublicKey(currentTime, &encryptingPriv.PublicKey),
|
||||
PrivateKey: packet.NewRSAPrivateKey(currentTime, encryptingPriv),
|
||||
Sig: &packet.Signature{
|
||||
CreationTime: currentTime,
|
||||
SigType: packet.SigTypeSubkeyBinding,
|
||||
|
@ -433,6 +434,8 @@ func NewEntity(rand io.Reader, currentTime time.Time, name, comment, email strin
|
|||
IssuerKeyId: &e.PrimaryKey.KeyId,
|
||||
},
|
||||
}
|
||||
e.Subkeys[0].PublicKey.IsSubkey = true
|
||||
e.Subkeys[0].PrivateKey.IsSubkey = true
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
@ -450,7 +453,7 @@ func (e *Entity) SerializePrivate(w io.Writer) (err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = ident.SelfSignature.SignUserId(ident.UserId.Id, e.PrimaryKey, e.PrivateKey)
|
||||
err = ident.SelfSignature.SignUserId(rand.Reader, ident.UserId.Id, e.PrimaryKey, e.PrivateKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -464,7 +467,7 @@ func (e *Entity) SerializePrivate(w io.Writer) (err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey)
|
||||
err = subkey.Sig.SignKey(rand.Reader, subkey.PublicKey, e.PrivateKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -518,14 +521,14 @@ func (e *Entity) Serialize(w io.Writer) error {
|
|||
// necessary.
|
||||
func (e *Entity) SignIdentity(identity string, signer *Entity) error {
|
||||
if signer.PrivateKey == nil {
|
||||
return error_.InvalidArgumentError("signing Entity must have a private key")
|
||||
return errors.InvalidArgumentError("signing Entity must have a private key")
|
||||
}
|
||||
if signer.PrivateKey.Encrypted {
|
||||
return error_.InvalidArgumentError("signing Entity's private key must be decrypted")
|
||||
return errors.InvalidArgumentError("signing Entity's private key must be decrypted")
|
||||
}
|
||||
ident, ok := e.Identities[identity]
|
||||
if !ok {
|
||||
return error_.InvalidArgumentError("given identity string not found in Entity")
|
||||
return errors.InvalidArgumentError("given identity string not found in Entity")
|
||||
}
|
||||
|
||||
sig := &packet.Signature{
|
||||
|
@ -535,7 +538,7 @@ func (e *Entity) SignIdentity(identity string, signer *Entity) error {
|
|||
CreationTime: time.Now(),
|
||||
IssuerKeyId: &signer.PrivateKey.KeyId,
|
||||
}
|
||||
if err := sig.SignKey(e.PrimaryKey, signer.PrivateKey); err != nil {
|
||||
if err := sig.SignKey(rand.Reader, e.PrimaryKey, signer.PrivateKey); err != nil {
|
||||
return err
|
||||
}
|
||||
ident.Signatures = append(ident.Signatures, sig)
|
||||
|
|
|
@ -7,7 +7,7 @@ package packet
|
|||
import (
|
||||
"compress/flate"
|
||||
"compress/zlib"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
@ -31,7 +31,7 @@ func (c *Compressed) parse(r io.Reader) error {
|
|||
case 2:
|
||||
c.Body, err = zlib.NewReader(r)
|
||||
default:
|
||||
err = error_.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0])))
|
||||
err = errors.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0])))
|
||||
}
|
||||
|
||||
return err
|
||||
|
|
|
@ -6,7 +6,7 @@ package packet
|
|||
|
||||
import (
|
||||
"crypto/openpgp/elgamal"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
|
@ -35,7 +35,7 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) {
|
|||
return
|
||||
}
|
||||
if buf[0] != encryptedKeyVersion {
|
||||
return error_.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0])))
|
||||
return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0])))
|
||||
}
|
||||
e.KeyId = binary.BigEndian.Uint64(buf[1:9])
|
||||
e.Algo = PublicKeyAlgorithm(buf[9])
|
||||
|
@ -77,7 +77,7 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey) error {
|
|||
c2 := new(big.Int).SetBytes(e.encryptedMPI2)
|
||||
b, err = elgamal.Decrypt(priv.PrivateKey.(*elgamal.PrivateKey), c1, c2)
|
||||
default:
|
||||
err = error_.InvalidArgumentError("cannot decrypted encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo)))
|
||||
err = errors.InvalidArgumentError("cannot decrypted encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo)))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -89,7 +89,7 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey) error {
|
|||
expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1])
|
||||
checksum := checksumKeyMaterial(e.Key)
|
||||
if checksum != expectedChecksum {
|
||||
return error_.StructuralError("EncryptedKey checksum incorrect")
|
||||
return errors.StructuralError("EncryptedKey checksum incorrect")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -116,16 +116,16 @@ func SerializeEncryptedKey(w io.Writer, rand io.Reader, pub *PublicKey, cipherFu
|
|||
case PubKeyAlgoElGamal:
|
||||
return serializeEncryptedKeyElGamal(w, rand, buf, pub.PublicKey.(*elgamal.PublicKey), keyBlock)
|
||||
case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly:
|
||||
return error_.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
|
||||
return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
|
||||
}
|
||||
|
||||
return error_.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
|
||||
return errors.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
|
||||
}
|
||||
|
||||
func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header [10]byte, pub *rsa.PublicKey, keyBlock []byte) error {
|
||||
cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock)
|
||||
if err != nil {
|
||||
return error_.InvalidArgumentError("RSA encryption failed: " + err.Error())
|
||||
return errors.InvalidArgumentError("RSA encryption failed: " + err.Error())
|
||||
}
|
||||
|
||||
packetLen := 10 /* header length */ + 2 /* mpi size */ + len(cipherText)
|
||||
|
@ -144,7 +144,7 @@ func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header [10]byte, pub
|
|||
func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header [10]byte, pub *elgamal.PublicKey, keyBlock []byte) error {
|
||||
c1, c2, err := elgamal.Encrypt(rand, pub, keyBlock)
|
||||
if err != nil {
|
||||
return error_.InvalidArgumentError("ElGamal encryption failed: " + err.Error())
|
||||
return errors.InvalidArgumentError("ElGamal encryption failed: " + err.Error())
|
||||
}
|
||||
|
||||
packetLen := 10 /* header length */
|
||||
|
|
|
@ -6,7 +6,7 @@ package packet
|
|||
|
||||
import (
|
||||
"crypto"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"crypto/openpgp/s2k"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
@ -33,13 +33,13 @@ func (ops *OnePassSignature) parse(r io.Reader) (err error) {
|
|||
return
|
||||
}
|
||||
if buf[0] != onePassSignatureVersion {
|
||||
err = error_.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0])))
|
||||
err = errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0])))
|
||||
}
|
||||
|
||||
var ok bool
|
||||
ops.Hash, ok = s2k.HashIdToHash(buf[2])
|
||||
if !ok {
|
||||
return error_.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2])))
|
||||
return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2])))
|
||||
}
|
||||
|
||||
ops.SigType = SignatureType(buf[1])
|
||||
|
@ -57,7 +57,7 @@ func (ops *OnePassSignature) Serialize(w io.Writer) error {
|
|||
var ok bool
|
||||
buf[2], ok = s2k.HashToHashId(ops.Hash)
|
||||
if !ok {
|
||||
return error_.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
|
||||
return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
|
||||
}
|
||||
buf[3] = uint8(ops.PubKeyAlgo)
|
||||
binary.BigEndian.PutUint64(buf[4:12], ops.KeyId)
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"crypto/aes"
|
||||
"crypto/cast5"
|
||||
"crypto/cipher"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
@ -162,7 +162,7 @@ func readHeader(r io.Reader) (tag packetType, length int64, contents io.Reader,
|
|||
return
|
||||
}
|
||||
if buf[0]&0x80 == 0 {
|
||||
err = error_.StructuralError("tag byte does not have MSB set")
|
||||
err = errors.StructuralError("tag byte does not have MSB set")
|
||||
return
|
||||
}
|
||||
if buf[0]&0x40 == 0 {
|
||||
|
@ -337,7 +337,7 @@ func Read(r io.Reader) (p Packet, err error) {
|
|||
se.MDC = true
|
||||
p = se
|
||||
default:
|
||||
err = error_.UnknownPacketTypeError(tag)
|
||||
err = errors.UnknownPacketTypeError(tag)
|
||||
}
|
||||
if p != nil {
|
||||
err = p.parse(contents)
|
||||
|
|
|
@ -6,7 +6,7 @@ package packet
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -152,7 +152,7 @@ func TestReadHeader(t *testing.T) {
|
|||
for i, test := range readHeaderTests {
|
||||
tag, length, contents, err := readHeader(readerFromHex(test.hexInput))
|
||||
if test.structuralError {
|
||||
if _, ok := err.(error_.StructuralError); ok {
|
||||
if _, ok := err.(errors.StructuralError); ok {
|
||||
continue
|
||||
}
|
||||
t.Errorf("%d: expected StructuralError, got:%s", i, err)
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"crypto/cipher"
|
||||
"crypto/dsa"
|
||||
"crypto/openpgp/elgamal"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"crypto/openpgp/s2k"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
|
@ -28,14 +28,21 @@ type PrivateKey struct {
|
|||
encryptedData []byte
|
||||
cipher CipherFunction
|
||||
s2k func(out, in []byte)
|
||||
PrivateKey interface{} // An *rsa.PrivateKey.
|
||||
PrivateKey interface{} // An *rsa.PrivateKey or *dsa.PrivateKey.
|
||||
sha1Checksum bool
|
||||
iv []byte
|
||||
}
|
||||
|
||||
func NewRSAPrivateKey(currentTime time.Time, priv *rsa.PrivateKey, isSubkey bool) *PrivateKey {
|
||||
func NewRSAPrivateKey(currentTime time.Time, priv *rsa.PrivateKey) *PrivateKey {
|
||||
pk := new(PrivateKey)
|
||||
pk.PublicKey = *NewRSAPublicKey(currentTime, &priv.PublicKey, isSubkey)
|
||||
pk.PublicKey = *NewRSAPublicKey(currentTime, &priv.PublicKey)
|
||||
pk.PrivateKey = priv
|
||||
return pk
|
||||
}
|
||||
|
||||
func NewDSAPrivateKey(currentTime time.Time, priv *dsa.PrivateKey) *PrivateKey {
|
||||
pk := new(PrivateKey)
|
||||
pk.PublicKey = *NewDSAPublicKey(currentTime, &priv.PublicKey)
|
||||
pk.PrivateKey = priv
|
||||
return pk
|
||||
}
|
||||
|
@ -72,13 +79,13 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) {
|
|||
pk.sha1Checksum = true
|
||||
}
|
||||
default:
|
||||
return error_.UnsupportedError("deprecated s2k function in private key")
|
||||
return errors.UnsupportedError("deprecated s2k function in private key")
|
||||
}
|
||||
|
||||
if pk.Encrypted {
|
||||
blockSize := pk.cipher.blockSize()
|
||||
if blockSize == 0 {
|
||||
return error_.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher)))
|
||||
return errors.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher)))
|
||||
}
|
||||
pk.iv = make([]byte, blockSize)
|
||||
_, err = readFull(r, pk.iv)
|
||||
|
@ -121,8 +128,10 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) {
|
|||
switch priv := pk.PrivateKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
err = serializeRSAPrivateKey(privateKeyBuf, priv)
|
||||
case *dsa.PrivateKey:
|
||||
err = serializeDSAPrivateKey(privateKeyBuf, priv)
|
||||
default:
|
||||
err = error_.InvalidArgumentError("non-RSA private key")
|
||||
err = errors.InvalidArgumentError("unknown private key type")
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -172,6 +181,10 @@ func serializeRSAPrivateKey(w io.Writer, priv *rsa.PrivateKey) error {
|
|||
return writeBig(w, priv.Precomputed.Qinv)
|
||||
}
|
||||
|
||||
func serializeDSAPrivateKey(w io.Writer, priv *dsa.PrivateKey) error {
|
||||
return writeBig(w, priv.X)
|
||||
}
|
||||
|
||||
// Decrypt decrypts an encrypted private key using a passphrase.
|
||||
func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
||||
if !pk.Encrypted {
|
||||
|
@ -188,18 +201,18 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
|||
|
||||
if pk.sha1Checksum {
|
||||
if len(data) < sha1.Size {
|
||||
return error_.StructuralError("truncated private key data")
|
||||
return errors.StructuralError("truncated private key data")
|
||||
}
|
||||
h := sha1.New()
|
||||
h.Write(data[:len(data)-sha1.Size])
|
||||
sum := h.Sum(nil)
|
||||
if !bytes.Equal(sum, data[len(data)-sha1.Size:]) {
|
||||
return error_.StructuralError("private key checksum failure")
|
||||
return errors.StructuralError("private key checksum failure")
|
||||
}
|
||||
data = data[:len(data)-sha1.Size]
|
||||
} else {
|
||||
if len(data) < 2 {
|
||||
return error_.StructuralError("truncated private key data")
|
||||
return errors.StructuralError("truncated private key data")
|
||||
}
|
||||
var sum uint16
|
||||
for i := 0; i < len(data)-2; i++ {
|
||||
|
@ -207,7 +220,7 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
|||
}
|
||||
if data[len(data)-2] != uint8(sum>>8) ||
|
||||
data[len(data)-1] != uint8(sum) {
|
||||
return error_.StructuralError("private key checksum failure")
|
||||
return errors.StructuralError("private key checksum failure")
|
||||
}
|
||||
data = data[:len(data)-2]
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ package packet
|
|||
import (
|
||||
"crypto/dsa"
|
||||
"crypto/openpgp/elgamal"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
|
@ -39,12 +39,11 @@ func fromBig(n *big.Int) parsedMPI {
|
|||
}
|
||||
|
||||
// NewRSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey.
|
||||
func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey, isSubkey bool) *PublicKey {
|
||||
func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey) *PublicKey {
|
||||
pk := &PublicKey{
|
||||
CreationTime: creationTime,
|
||||
PubKeyAlgo: PubKeyAlgoRSA,
|
||||
PublicKey: pub,
|
||||
IsSubkey: isSubkey,
|
||||
n: fromBig(pub.N),
|
||||
e: fromBig(big.NewInt(int64(pub.E))),
|
||||
}
|
||||
|
@ -53,6 +52,22 @@ func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey, isSubkey bool)
|
|||
return pk
|
||||
}
|
||||
|
||||
// NewDSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey.
|
||||
func NewDSAPublicKey(creationTime time.Time, pub *dsa.PublicKey) *PublicKey {
|
||||
pk := &PublicKey{
|
||||
CreationTime: creationTime,
|
||||
PubKeyAlgo: PubKeyAlgoDSA,
|
||||
PublicKey: pub,
|
||||
p: fromBig(pub.P),
|
||||
q: fromBig(pub.Q),
|
||||
g: fromBig(pub.G),
|
||||
y: fromBig(pub.Y),
|
||||
}
|
||||
|
||||
pk.setFingerPrintAndKeyId()
|
||||
return pk
|
||||
}
|
||||
|
||||
func (pk *PublicKey) parse(r io.Reader) (err error) {
|
||||
// RFC 4880, section 5.5.2
|
||||
var buf [6]byte
|
||||
|
@ -61,7 +76,7 @@ func (pk *PublicKey) parse(r io.Reader) (err error) {
|
|||
return
|
||||
}
|
||||
if buf[0] != 4 {
|
||||
return error_.UnsupportedError("public key version")
|
||||
return errors.UnsupportedError("public key version")
|
||||
}
|
||||
pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0)
|
||||
pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5])
|
||||
|
@ -73,7 +88,7 @@ func (pk *PublicKey) parse(r io.Reader) (err error) {
|
|||
case PubKeyAlgoElGamal:
|
||||
err = pk.parseElGamal(r)
|
||||
default:
|
||||
err = error_.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
|
||||
err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -105,7 +120,7 @@ func (pk *PublicKey) parseRSA(r io.Reader) (err error) {
|
|||
}
|
||||
|
||||
if len(pk.e.bytes) > 3 {
|
||||
err = error_.UnsupportedError("large public exponent")
|
||||
err = errors.UnsupportedError("large public exponent")
|
||||
return
|
||||
}
|
||||
rsa := &rsa.PublicKey{
|
||||
|
@ -255,7 +270,7 @@ func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) {
|
|||
case PubKeyAlgoElGamal:
|
||||
return writeMPIs(w, pk.p, pk.g, pk.y)
|
||||
}
|
||||
return error_.InvalidArgumentError("bad public-key algorithm")
|
||||
return errors.InvalidArgumentError("bad public-key algorithm")
|
||||
}
|
||||
|
||||
// CanSign returns true iff this public key can generate signatures
|
||||
|
@ -267,18 +282,18 @@ func (pk *PublicKey) CanSign() bool {
|
|||
// public key, of the data hashed into signed. signed is mutated by this call.
|
||||
func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err error) {
|
||||
if !pk.CanSign() {
|
||||
return error_.InvalidArgumentError("public key cannot generate signatures")
|
||||
return errors.InvalidArgumentError("public key cannot generate signatures")
|
||||
}
|
||||
|
||||
signed.Write(sig.HashSuffix)
|
||||
hashBytes := signed.Sum(nil)
|
||||
|
||||
if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
|
||||
return error_.SignatureError("hash tag doesn't match")
|
||||
return errors.SignatureError("hash tag doesn't match")
|
||||
}
|
||||
|
||||
if pk.PubKeyAlgo != sig.PubKeyAlgo {
|
||||
return error_.InvalidArgumentError("public key and signature use different algorithms")
|
||||
return errors.InvalidArgumentError("public key and signature use different algorithms")
|
||||
}
|
||||
|
||||
switch pk.PubKeyAlgo {
|
||||
|
@ -286,13 +301,18 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro
|
|||
rsaPublicKey, _ := pk.PublicKey.(*rsa.PublicKey)
|
||||
err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, sig.RSASignature.bytes)
|
||||
if err != nil {
|
||||
return error_.SignatureError("RSA verification failure")
|
||||
return errors.SignatureError("RSA verification failure")
|
||||
}
|
||||
return nil
|
||||
case PubKeyAlgoDSA:
|
||||
dsaPublicKey, _ := pk.PublicKey.(*dsa.PublicKey)
|
||||
// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
|
||||
subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
|
||||
if len(hashBytes) > subgroupSize {
|
||||
hashBytes = hashBytes[:subgroupSize]
|
||||
}
|
||||
if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {
|
||||
return error_.SignatureError("DSA verification failure")
|
||||
return errors.SignatureError("DSA verification failure")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
|
@ -306,7 +326,7 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro
|
|||
func keySignatureHash(pk, signed *PublicKey, sig *Signature) (h hash.Hash, err error) {
|
||||
h = sig.Hash.New()
|
||||
if h == nil {
|
||||
return nil, error_.UnsupportedError("hash function")
|
||||
return nil, errors.UnsupportedError("hash function")
|
||||
}
|
||||
|
||||
// RFC 4880, section 5.2.4
|
||||
|
@ -332,7 +352,7 @@ func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) (err
|
|||
func userIdSignatureHash(id string, pk *PublicKey, sig *Signature) (h hash.Hash, err error) {
|
||||
h = sig.Hash.New()
|
||||
if h == nil {
|
||||
return nil, error_.UnsupportedError("hash function")
|
||||
return nil, errors.UnsupportedError("hash function")
|
||||
}
|
||||
|
||||
// RFC 4880, section 5.2.4
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
package packet
|
||||
|
||||
import (
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
@ -34,7 +34,7 @@ func (r *Reader) Next() (p Packet, err error) {
|
|||
r.readers = r.readers[:len(r.readers)-1]
|
||||
continue
|
||||
}
|
||||
if _, ok := err.(error_.UnknownPacketTypeError); !ok {
|
||||
if _, ok := err.(errors.UnknownPacketTypeError); !ok {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,8 @@ package packet
|
|||
import (
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"crypto/openpgp/s2k"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
|
@ -61,7 +60,7 @@ func (sig *Signature) parse(r io.Reader) (err error) {
|
|||
return
|
||||
}
|
||||
if buf[0] != 4 {
|
||||
err = error_.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0])))
|
||||
err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0])))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -74,14 +73,14 @@ func (sig *Signature) parse(r io.Reader) (err error) {
|
|||
switch sig.PubKeyAlgo {
|
||||
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA:
|
||||
default:
|
||||
err = error_.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo)))
|
||||
err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo)))
|
||||
return
|
||||
}
|
||||
|
||||
var ok bool
|
||||
sig.Hash, ok = s2k.HashIdToHash(buf[2])
|
||||
if !ok {
|
||||
return error_.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
|
||||
return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
|
||||
}
|
||||
|
||||
hashedSubpacketsLength := int(buf[3])<<8 | int(buf[4])
|
||||
|
@ -153,7 +152,7 @@ func parseSignatureSubpackets(sig *Signature, subpackets []byte, isHashed bool)
|
|||
}
|
||||
|
||||
if sig.CreationTime.IsZero() {
|
||||
err = error_.StructuralError("no creation time in signature")
|
||||
err = errors.StructuralError("no creation time in signature")
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -164,7 +163,7 @@ type signatureSubpacketType uint8
|
|||
const (
|
||||
creationTimeSubpacket signatureSubpacketType = 2
|
||||
signatureExpirationSubpacket signatureSubpacketType = 3
|
||||
keyExpirySubpacket signatureSubpacketType = 9
|
||||
keyExpirationSubpacket signatureSubpacketType = 9
|
||||
prefSymmetricAlgosSubpacket signatureSubpacketType = 11
|
||||
issuerSubpacket signatureSubpacketType = 16
|
||||
prefHashAlgosSubpacket signatureSubpacketType = 21
|
||||
|
@ -207,7 +206,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
rest = subpacket[length:]
|
||||
subpacket = subpacket[:length]
|
||||
if len(subpacket) == 0 {
|
||||
err = error_.StructuralError("zero length signature subpacket")
|
||||
err = errors.StructuralError("zero length signature subpacket")
|
||||
return
|
||||
}
|
||||
packetType = signatureSubpacketType(subpacket[0] & 0x7f)
|
||||
|
@ -217,37 +216,33 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
switch packetType {
|
||||
case creationTimeSubpacket:
|
||||
if !isHashed {
|
||||
err = error_.StructuralError("signature creation time in non-hashed area")
|
||||
err = errors.StructuralError("signature creation time in non-hashed area")
|
||||
return
|
||||
}
|
||||
if len(subpacket) != 4 {
|
||||
err = error_.StructuralError("signature creation time not four bytes")
|
||||
err = errors.StructuralError("signature creation time not four bytes")
|
||||
return
|
||||
}
|
||||
t := binary.BigEndian.Uint32(subpacket)
|
||||
if t == 0 {
|
||||
sig.CreationTime = time.Time{}
|
||||
} else {
|
||||
sig.CreationTime = time.Unix(int64(t), 0)
|
||||
}
|
||||
sig.CreationTime = time.Unix(int64(t), 0)
|
||||
case signatureExpirationSubpacket:
|
||||
// Signature expiration time, section 5.2.3.10
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) != 4 {
|
||||
err = error_.StructuralError("expiration subpacket with bad length")
|
||||
err = errors.StructuralError("expiration subpacket with bad length")
|
||||
return
|
||||
}
|
||||
sig.SigLifetimeSecs = new(uint32)
|
||||
*sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket)
|
||||
case keyExpirySubpacket:
|
||||
case keyExpirationSubpacket:
|
||||
// Key expiration time, section 5.2.3.6
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) != 4 {
|
||||
err = error_.StructuralError("key expiration subpacket with bad length")
|
||||
err = errors.StructuralError("key expiration subpacket with bad length")
|
||||
return
|
||||
}
|
||||
sig.KeyLifetimeSecs = new(uint32)
|
||||
|
@ -262,7 +257,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
case issuerSubpacket:
|
||||
// Issuer, section 5.2.3.5
|
||||
if len(subpacket) != 8 {
|
||||
err = error_.StructuralError("issuer subpacket with bad length")
|
||||
err = errors.StructuralError("issuer subpacket with bad length")
|
||||
return
|
||||
}
|
||||
sig.IssuerKeyId = new(uint64)
|
||||
|
@ -287,7 +282,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
return
|
||||
}
|
||||
if len(subpacket) != 1 {
|
||||
err = error_.StructuralError("primary user id subpacket with bad length")
|
||||
err = errors.StructuralError("primary user id subpacket with bad length")
|
||||
return
|
||||
}
|
||||
sig.IsPrimaryId = new(bool)
|
||||
|
@ -300,7 +295,7 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
return
|
||||
}
|
||||
if len(subpacket) == 0 {
|
||||
err = error_.StructuralError("empty key flags subpacket")
|
||||
err = errors.StructuralError("empty key flags subpacket")
|
||||
return
|
||||
}
|
||||
sig.FlagsValid = true
|
||||
|
@ -319,14 +314,14 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
|
||||
default:
|
||||
if isCritical {
|
||||
err = error_.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType)))
|
||||
err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType)))
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
Truncated:
|
||||
err = error_.StructuralError("signature subpacket truncated")
|
||||
err = errors.StructuralError("signature subpacket truncated")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -401,7 +396,7 @@ func (sig *Signature) buildHashSuffix() (err error) {
|
|||
sig.HashSuffix[3], ok = s2k.HashToHashId(sig.Hash)
|
||||
if !ok {
|
||||
sig.HashSuffix = nil
|
||||
return error_.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash)))
|
||||
return errors.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash)))
|
||||
}
|
||||
sig.HashSuffix[4] = byte(hashedSubpacketsLen >> 8)
|
||||
sig.HashSuffix[5] = byte(hashedSubpacketsLen)
|
||||
|
@ -431,7 +426,7 @@ func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) {
|
|||
// Sign signs a message with a private key. The hash, h, must contain
|
||||
// the hash of the message to be signed and will be mutated by this function.
|
||||
// On success, the signature is stored in sig. Call Serialize to write it out.
|
||||
func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey) (err error) {
|
||||
func (sig *Signature) Sign(rand io.Reader, h hash.Hash, priv *PrivateKey) (err error) {
|
||||
sig.outSubpackets = sig.buildSubpackets()
|
||||
digest, err := sig.signPrepareHash(h)
|
||||
if err != nil {
|
||||
|
@ -440,10 +435,17 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey) (err error) {
|
|||
|
||||
switch priv.PubKeyAlgo {
|
||||
case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
|
||||
sig.RSASignature.bytes, err = rsa.SignPKCS1v15(rand.Reader, priv.PrivateKey.(*rsa.PrivateKey), sig.Hash, digest)
|
||||
sig.RSASignature.bytes, err = rsa.SignPKCS1v15(rand, priv.PrivateKey.(*rsa.PrivateKey), sig.Hash, digest)
|
||||
sig.RSASignature.bitLength = uint16(8 * len(sig.RSASignature.bytes))
|
||||
case PubKeyAlgoDSA:
|
||||
r, s, err := dsa.Sign(rand.Reader, priv.PrivateKey.(*dsa.PrivateKey), digest)
|
||||
dsaPriv := priv.PrivateKey.(*dsa.PrivateKey)
|
||||
|
||||
// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
|
||||
subgroupSize := (dsaPriv.Q.BitLen() + 7) / 8
|
||||
if len(digest) > subgroupSize {
|
||||
digest = digest[:subgroupSize]
|
||||
}
|
||||
r, s, err := dsa.Sign(rand, dsaPriv, digest)
|
||||
if err == nil {
|
||||
sig.DSASigR.bytes = r.Bytes()
|
||||
sig.DSASigR.bitLength = uint16(8 * len(sig.DSASigR.bytes))
|
||||
|
@ -451,7 +453,7 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey) (err error) {
|
|||
sig.DSASigS.bitLength = uint16(8 * len(sig.DSASigS.bytes))
|
||||
}
|
||||
default:
|
||||
err = error_.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo)))
|
||||
err = errors.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo)))
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -460,22 +462,22 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey) (err error) {
|
|||
// SignUserId computes a signature from priv, asserting that pub is a valid
|
||||
// key for the identity id. On success, the signature is stored in sig. Call
|
||||
// Serialize to write it out.
|
||||
func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey) error {
|
||||
func (sig *Signature) SignUserId(rand io.Reader, id string, pub *PublicKey, priv *PrivateKey) error {
|
||||
h, err := userIdSignatureHash(id, pub, sig)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return sig.Sign(h, priv)
|
||||
return sig.Sign(rand, h, priv)
|
||||
}
|
||||
|
||||
// SignKey computes a signature from priv, asserting that pub is a subkey. On
|
||||
// success, the signature is stored in sig. Call Serialize to write it out.
|
||||
func (sig *Signature) SignKey(pub *PublicKey, priv *PrivateKey) error {
|
||||
func (sig *Signature) SignKey(rand io.Reader, pub *PublicKey, priv *PrivateKey) error {
|
||||
h, err := keySignatureHash(&priv.PublicKey, pub, sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sig.Sign(h, priv)
|
||||
return sig.Sign(rand, h, priv)
|
||||
}
|
||||
|
||||
// Serialize marshals sig to w. SignRSA or SignDSA must have been called first.
|
||||
|
@ -484,7 +486,7 @@ func (sig *Signature) Serialize(w io.Writer) (err error) {
|
|||
sig.outSubpackets = sig.rawSubpackets
|
||||
}
|
||||
if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil {
|
||||
return error_.InvalidArgumentError("Signature: need to call SignRSA or SignDSA before Serialize")
|
||||
return errors.InvalidArgumentError("Signature: need to call SignRSA or SignDSA before Serialize")
|
||||
}
|
||||
|
||||
sigLength := 0
|
||||
|
@ -556,5 +558,54 @@ func (sig *Signature) buildSubpackets() (subpackets []outputSubpacket) {
|
|||
subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId})
|
||||
}
|
||||
|
||||
if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 {
|
||||
sigLifetime := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(sigLifetime, *sig.SigLifetimeSecs)
|
||||
subpackets = append(subpackets, outputSubpacket{true, signatureExpirationSubpacket, true, sigLifetime})
|
||||
}
|
||||
|
||||
// Key flags may only appear in self-signatures or certification signatures.
|
||||
|
||||
if sig.FlagsValid {
|
||||
var flags byte
|
||||
if sig.FlagCertify {
|
||||
flags |= 1
|
||||
}
|
||||
if sig.FlagSign {
|
||||
flags |= 2
|
||||
}
|
||||
if sig.FlagEncryptCommunications {
|
||||
flags |= 4
|
||||
}
|
||||
if sig.FlagEncryptStorage {
|
||||
flags |= 8
|
||||
}
|
||||
subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}})
|
||||
}
|
||||
|
||||
// The following subpackets may only appear in self-signatures
|
||||
|
||||
if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 {
|
||||
keyLifetime := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs)
|
||||
subpackets = append(subpackets, outputSubpacket{true, keyExpirationSubpacket, true, keyLifetime})
|
||||
}
|
||||
|
||||
if sig.IsPrimaryId != nil && *sig.IsPrimaryId {
|
||||
subpackets = append(subpackets, outputSubpacket{true, primaryUserIdSubpacket, false, []byte{1}})
|
||||
}
|
||||
|
||||
if len(sig.PreferredSymmetric) > 0 {
|
||||
subpackets = append(subpackets, outputSubpacket{true, prefSymmetricAlgosSubpacket, false, sig.PreferredSymmetric})
|
||||
}
|
||||
|
||||
if len(sig.PreferredHash) > 0 {
|
||||
subpackets = append(subpackets, outputSubpacket{true, prefHashAlgosSubpacket, false, sig.PreferredHash})
|
||||
}
|
||||
|
||||
if len(sig.PreferredCompression) > 0 {
|
||||
subpackets = append(subpackets, outputSubpacket{true, prefCompressionSubpacket, false, sig.PreferredCompression})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ package packet
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"crypto/openpgp/s2k"
|
||||
"io"
|
||||
"strconv"
|
||||
|
@ -37,12 +37,12 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) (err error) {
|
|||
return
|
||||
}
|
||||
if buf[0] != symmetricKeyEncryptedVersion {
|
||||
return error_.UnsupportedError("SymmetricKeyEncrypted version")
|
||||
return errors.UnsupportedError("SymmetricKeyEncrypted version")
|
||||
}
|
||||
ske.CipherFunc = CipherFunction(buf[1])
|
||||
|
||||
if ske.CipherFunc.KeySize() == 0 {
|
||||
return error_.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[1])))
|
||||
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[1])))
|
||||
}
|
||||
|
||||
ske.s2k, err = s2k.Parse(r)
|
||||
|
@ -60,7 +60,7 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) (err error) {
|
|||
err = nil
|
||||
if n != 0 {
|
||||
if n == maxSessionKeySizeInBytes {
|
||||
return error_.UnsupportedError("oversized encrypted session key")
|
||||
return errors.UnsupportedError("oversized encrypted session key")
|
||||
}
|
||||
ske.encryptedKey = encryptedKey[:n]
|
||||
}
|
||||
|
@ -89,13 +89,13 @@ func (ske *SymmetricKeyEncrypted) Decrypt(passphrase []byte) error {
|
|||
c.XORKeyStream(ske.encryptedKey, ske.encryptedKey)
|
||||
ske.CipherFunc = CipherFunction(ske.encryptedKey[0])
|
||||
if ske.CipherFunc.blockSize() == 0 {
|
||||
return error_.UnsupportedError("unknown cipher: " + strconv.Itoa(int(ske.CipherFunc)))
|
||||
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(ske.CipherFunc)))
|
||||
}
|
||||
ske.CipherFunc = CipherFunction(ske.encryptedKey[0])
|
||||
ske.Key = ske.encryptedKey[1:]
|
||||
if len(ske.Key)%ske.CipherFunc.blockSize() != 0 {
|
||||
ske.Key = nil
|
||||
return error_.StructuralError("length of decrypted key not a multiple of block size")
|
||||
return errors.StructuralError("length of decrypted key not a multiple of block size")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ func (ske *SymmetricKeyEncrypted) Decrypt(passphrase []byte) error {
|
|||
func SerializeSymmetricKeyEncrypted(w io.Writer, rand io.Reader, passphrase []byte, cipherFunc CipherFunction) (key []byte, err error) {
|
||||
keySize := cipherFunc.KeySize()
|
||||
if keySize == 0 {
|
||||
return nil, error_.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc)))
|
||||
return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc)))
|
||||
}
|
||||
|
||||
s2kBuf := new(bytes.Buffer)
|
||||
|
|
|
@ -6,8 +6,7 @@ package packet
|
|||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/rand"
|
||||
"crypto/openpgp/errors"
|
||||
"crypto/sha1"
|
||||
"crypto/subtle"
|
||||
"hash"
|
||||
|
@ -35,7 +34,7 @@ func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
|
|||
return err
|
||||
}
|
||||
if buf[0] != symmetricallyEncryptedVersion {
|
||||
return error_.UnsupportedError("unknown SymmetricallyEncrypted version")
|
||||
return errors.UnsupportedError("unknown SymmetricallyEncrypted version")
|
||||
}
|
||||
}
|
||||
se.contents = r
|
||||
|
@ -48,10 +47,10 @@ func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
|
|||
func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) {
|
||||
keySize := c.KeySize()
|
||||
if keySize == 0 {
|
||||
return nil, error_.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c)))
|
||||
return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c)))
|
||||
}
|
||||
if len(key) != keySize {
|
||||
return nil, error_.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
|
||||
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
|
||||
}
|
||||
|
||||
if se.prefix == nil {
|
||||
|
@ -61,7 +60,7 @@ func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.Read
|
|||
return nil, err
|
||||
}
|
||||
} else if len(se.prefix) != c.blockSize()+2 {
|
||||
return nil, error_.InvalidArgumentError("can't try ciphers with different block lengths")
|
||||
return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths")
|
||||
}
|
||||
|
||||
ocfbResync := cipher.OCFBResync
|
||||
|
@ -72,7 +71,7 @@ func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.Read
|
|||
|
||||
s := cipher.NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync)
|
||||
if s == nil {
|
||||
return nil, error_.KeyIncorrectError
|
||||
return nil, errors.KeyIncorrectError
|
||||
}
|
||||
|
||||
plaintext := cipher.StreamReader{S: s, R: se.contents}
|
||||
|
@ -181,7 +180,7 @@ const mdcPacketTagByte = byte(0x80) | 0x40 | 19
|
|||
|
||||
func (ser *seMDCReader) Close() error {
|
||||
if ser.error {
|
||||
return error_.SignatureError("error during reading")
|
||||
return errors.SignatureError("error during reading")
|
||||
}
|
||||
|
||||
for !ser.eof {
|
||||
|
@ -192,18 +191,18 @@ func (ser *seMDCReader) Close() error {
|
|||
break
|
||||
}
|
||||
if err != nil {
|
||||
return error_.SignatureError("error during reading")
|
||||
return errors.SignatureError("error during reading")
|
||||
}
|
||||
}
|
||||
|
||||
if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size {
|
||||
return error_.SignatureError("MDC packet not found")
|
||||
return errors.SignatureError("MDC packet not found")
|
||||
}
|
||||
ser.h.Write(ser.trailer[:2])
|
||||
|
||||
final := ser.h.Sum(nil)
|
||||
if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 {
|
||||
return error_.SignatureError("hash mismatch")
|
||||
return errors.SignatureError("hash mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -253,9 +252,9 @@ func (c noOpCloser) Close() error {
|
|||
// SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet
|
||||
// to w and returns a WriteCloser to which the to-be-encrypted packets can be
|
||||
// written.
|
||||
func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, key []byte) (contents io.WriteCloser, err error) {
|
||||
func SerializeSymmetricallyEncrypted(w io.Writer, rand io.Reader, c CipherFunction, key []byte) (contents io.WriteCloser, err error) {
|
||||
if c.KeySize() != len(key) {
|
||||
return nil, error_.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length")
|
||||
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length")
|
||||
}
|
||||
writeCloser := noOpCloser{w}
|
||||
ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC)
|
||||
|
@ -271,7 +270,7 @@ func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, key []byte)
|
|||
block := c.new(key)
|
||||
blockSize := block.BlockSize()
|
||||
iv := make([]byte, blockSize)
|
||||
_, err = rand.Reader.Read(iv)
|
||||
_, err = rand.Read(iv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@ package packet
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
|
@ -70,7 +71,7 @@ func testMDCReader(t *testing.T) {
|
|||
err = mdcReader.Close()
|
||||
if err == nil {
|
||||
t.Error("corruption: no error")
|
||||
} else if _, ok := err.(*error_.SignatureError); !ok {
|
||||
} else if _, ok := err.(*errors.SignatureError); !ok {
|
||||
t.Errorf("corruption: expected SignatureError, got: %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +83,7 @@ func TestSerialize(t *testing.T) {
|
|||
c := CipherAES128
|
||||
key := make([]byte, c.KeySize())
|
||||
|
||||
w, err := SerializeSymmetricallyEncrypted(buf, c, key)
|
||||
w, err := SerializeSymmetricallyEncrypted(buf, rand.Reader, c, key)
|
||||
if err != nil {
|
||||
t.Errorf("error from SerializeSymmetricallyEncrypted: %s", err)
|
||||
return
|
||||
|
|
|
@ -8,7 +8,7 @@ package openpgp
|
|||
import (
|
||||
"crypto"
|
||||
"crypto/openpgp/armor"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"crypto/openpgp/packet"
|
||||
_ "crypto/sha256"
|
||||
"hash"
|
||||
|
@ -27,7 +27,7 @@ func readArmored(r io.Reader, expectedType string) (body io.Reader, err error) {
|
|||
}
|
||||
|
||||
if block.Type != expectedType {
|
||||
return nil, error_.InvalidArgumentError("expected '" + expectedType + "', got: " + block.Type)
|
||||
return nil, errors.InvalidArgumentError("expected '" + expectedType + "', got: " + block.Type)
|
||||
}
|
||||
|
||||
return block.Body, nil
|
||||
|
@ -130,7 +130,7 @@ ParsePackets:
|
|||
case *packet.Compressed, *packet.LiteralData, *packet.OnePassSignature:
|
||||
// This message isn't encrypted.
|
||||
if len(symKeys) != 0 || len(pubKeys) != 0 {
|
||||
return nil, error_.StructuralError("key material not followed by encrypted message")
|
||||
return nil, errors.StructuralError("key material not followed by encrypted message")
|
||||
}
|
||||
packets.Unread(p)
|
||||
return readSignedMessage(packets, nil, keyring)
|
||||
|
@ -161,7 +161,7 @@ FindKey:
|
|||
continue
|
||||
}
|
||||
decrypted, err = se.Decrypt(pk.encryptedKey.CipherFunc, pk.encryptedKey.Key)
|
||||
if err != nil && err != error_.KeyIncorrectError {
|
||||
if err != nil && err != errors.KeyIncorrectError {
|
||||
return nil, err
|
||||
}
|
||||
if decrypted != nil {
|
||||
|
@ -179,11 +179,11 @@ FindKey:
|
|||
}
|
||||
|
||||
if len(candidates) == 0 && len(symKeys) == 0 {
|
||||
return nil, error_.KeyIncorrectError
|
||||
return nil, errors.KeyIncorrectError
|
||||
}
|
||||
|
||||
if prompt == nil {
|
||||
return nil, error_.KeyIncorrectError
|
||||
return nil, errors.KeyIncorrectError
|
||||
}
|
||||
|
||||
passphrase, err := prompt(candidates, len(symKeys) != 0)
|
||||
|
@ -197,7 +197,7 @@ FindKey:
|
|||
err = s.Decrypt(passphrase)
|
||||
if err == nil && !s.Encrypted {
|
||||
decrypted, err = se.Decrypt(s.CipherFunc, s.Key)
|
||||
if err != nil && err != error_.KeyIncorrectError {
|
||||
if err != nil && err != errors.KeyIncorrectError {
|
||||
return nil, err
|
||||
}
|
||||
if decrypted != nil {
|
||||
|
@ -237,7 +237,7 @@ FindLiteralData:
|
|||
packets.Push(p.Body)
|
||||
case *packet.OnePassSignature:
|
||||
if !p.IsLast {
|
||||
return nil, error_.UnsupportedError("nested signatures")
|
||||
return nil, errors.UnsupportedError("nested signatures")
|
||||
}
|
||||
|
||||
h, wrappedHash, err = hashForSignature(p.Hash, p.SigType)
|
||||
|
@ -281,7 +281,7 @@ FindLiteralData:
|
|||
func hashForSignature(hashId crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) {
|
||||
h := hashId.New()
|
||||
if h == nil {
|
||||
return nil, nil, error_.UnsupportedError("hash not available: " + strconv.Itoa(int(hashId)))
|
||||
return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashId)))
|
||||
}
|
||||
|
||||
switch sigType {
|
||||
|
@ -291,7 +291,7 @@ func hashForSignature(hashId crypto.Hash, sigType packet.SignatureType) (hash.Ha
|
|||
return h, NewCanonicalTextHash(h), nil
|
||||
}
|
||||
|
||||
return nil, nil, error_.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType)))
|
||||
return nil, nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType)))
|
||||
}
|
||||
|
||||
// checkReader wraps an io.Reader from a LiteralData packet. When it sees EOF
|
||||
|
@ -333,7 +333,7 @@ func (scr *signatureCheckReader) Read(buf []byte) (n int, err error) {
|
|||
|
||||
var ok bool
|
||||
if scr.md.Signature, ok = p.(*packet.Signature); !ok {
|
||||
scr.md.SignatureError = error_.StructuralError("LiteralData not followed by Signature")
|
||||
scr.md.SignatureError = errors.StructuralError("LiteralData not followed by Signature")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -363,16 +363,16 @@ func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signe
|
|||
|
||||
sig, ok := p.(*packet.Signature)
|
||||
if !ok {
|
||||
return nil, error_.StructuralError("non signature packet found")
|
||||
return nil, errors.StructuralError("non signature packet found")
|
||||
}
|
||||
|
||||
if sig.IssuerKeyId == nil {
|
||||
return nil, error_.StructuralError("signature doesn't have an issuer")
|
||||
return nil, errors.StructuralError("signature doesn't have an issuer")
|
||||
}
|
||||
|
||||
keys := keyring.KeysById(*sig.IssuerKeyId)
|
||||
if len(keys) == 0 {
|
||||
return nil, error_.UnknownIssuerError
|
||||
return nil, errors.UnknownIssuerError
|
||||
}
|
||||
|
||||
h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
|
||||
|
@ -399,7 +399,7 @@ func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signe
|
|||
return
|
||||
}
|
||||
|
||||
return nil, error_.UnknownIssuerError
|
||||
return nil, errors.UnknownIssuerError
|
||||
}
|
||||
|
||||
// CheckArmoredDetachedSignature performs the same actions as
|
||||
|
|
|
@ -6,7 +6,8 @@ package openpgp
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
_ "crypto/sha512"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -77,6 +78,15 @@ func TestReadDSAKey(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDSAHashTruncatation(t *testing.T) {
|
||||
// dsaKeyWithSHA512 was generated with GnuPG and --cert-digest-algo
|
||||
// SHA512 in order to require DSA hash truncation to verify correctly.
|
||||
_, err := ReadKeyRing(readerFromHex(dsaKeyWithSHA512))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetKeyById(t *testing.T) {
|
||||
kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
|
||||
|
||||
|
@ -151,18 +161,18 @@ func TestSignedEncryptedMessage(t *testing.T) {
|
|||
prompt := func(keys []Key, symmetric bool) ([]byte, error) {
|
||||
if symmetric {
|
||||
t.Errorf("prompt: message was marked as symmetrically encrypted")
|
||||
return nil, error_.KeyIncorrectError
|
||||
return nil, errors.KeyIncorrectError
|
||||
}
|
||||
|
||||
if len(keys) == 0 {
|
||||
t.Error("prompt: no keys requested")
|
||||
return nil, error_.KeyIncorrectError
|
||||
return nil, errors.KeyIncorrectError
|
||||
}
|
||||
|
||||
err := keys[0].PrivateKey.Decrypt([]byte("passphrase"))
|
||||
if err != nil {
|
||||
t.Errorf("prompt: error decrypting key: %s", err)
|
||||
return nil, error_.KeyIncorrectError
|
||||
return nil, errors.KeyIncorrectError
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
@ -286,7 +296,7 @@ func TestReadingArmoredPrivateKey(t *testing.T) {
|
|||
|
||||
func TestNoArmoredData(t *testing.T) {
|
||||
_, err := ReadArmoredKeyRing(bytes.NewBufferString("foo"))
|
||||
if _, ok := err.(error_.InvalidArgumentError); !ok {
|
||||
if _, ok := err.(errors.InvalidArgumentError); !ok {
|
||||
t.Errorf("error was not an InvalidArgumentError: %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -358,3 +368,5 @@ AHcVnXjtxrULkQFGbGvhKURLvS9WnzD/m1K2zzwxzkPTzT9/Yf06O6Mal5AdugPL
|
|||
VrM0m72/jnpKo04=
|
||||
=zNCn
|
||||
-----END PGP PRIVATE KEY BLOCK-----`
|
||||
|
||||
const dsaKeyWithSHA512 = `9901a2044f04b07f110400db244efecc7316553ee08d179972aab87bb1214de7692593fcf5b6feb1c80fba268722dd464748539b85b81d574cd2d7ad0ca2444de4d849b8756bad7768c486c83a824f9bba4af773d11742bdfb4ac3b89ef8cc9452d4aad31a37e4b630d33927bff68e879284a1672659b8b298222fc68f370f3e24dccacc4a862442b9438b00a0ea444a24088dc23e26df7daf8f43cba3bffc4fe703fe3d6cd7fdca199d54ed8ae501c30e3ec7871ea9cdd4cf63cfe6fc82281d70a5b8bb493f922cd99fba5f088935596af087c8d818d5ec4d0b9afa7f070b3d7c1dd32a84fca08d8280b4890c8da1dde334de8e3cad8450eed2a4a4fcc2db7b8e5528b869a74a7f0189e11ef097ef1253582348de072bb07a9fa8ab838e993cef0ee203ff49298723e2d1f549b00559f886cd417a41692ce58d0ac1307dc71d85a8af21b0cf6eaa14baf2922d3a70389bedf17cc514ba0febbd107675a372fe84b90162a9e88b14d4b1c6be855b96b33fb198c46f058568817780435b6936167ebb3724b680f32bf27382ada2e37a879b3d9de2abe0c3f399350afd1ad438883f4791e2e3b4184453412068617368207472756e636174696f6e207465737488620413110a002205024f04b07f021b03060b090807030206150802090a0b0416020301021e01021780000a0910ef20e0cefca131581318009e2bf3bf047a44d75a9bacd00161ee04d435522397009a03a60d51bd8a568c6c021c8d7cf1be8d990d6417b0020003`
|
||||
|
|
|
@ -8,7 +8,7 @@ package s2k
|
|||
|
||||
import (
|
||||
"crypto"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"hash"
|
||||
"io"
|
||||
"strconv"
|
||||
|
@ -89,11 +89,11 @@ func Parse(r io.Reader) (f func(out, in []byte), err error) {
|
|||
|
||||
hash, ok := HashIdToHash(buf[1])
|
||||
if !ok {
|
||||
return nil, error_.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(buf[1])))
|
||||
return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(buf[1])))
|
||||
}
|
||||
h := hash.New()
|
||||
if h == nil {
|
||||
return nil, error_.UnsupportedError("hash not available: " + strconv.Itoa(int(hash)))
|
||||
return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hash)))
|
||||
}
|
||||
|
||||
switch buf[0] {
|
||||
|
@ -123,7 +123,7 @@ func Parse(r io.Reader) (f func(out, in []byte), err error) {
|
|||
return f, nil
|
||||
}
|
||||
|
||||
return nil, error_.UnsupportedError("S2K function")
|
||||
return nil, errors.UnsupportedError("S2K function")
|
||||
}
|
||||
|
||||
// Serialize salts and stretches the given passphrase and writes the resulting
|
||||
|
|
|
@ -7,7 +7,7 @@ package openpgp
|
|||
import (
|
||||
"crypto"
|
||||
"crypto/openpgp/armor"
|
||||
error_ "crypto/openpgp/error"
|
||||
"crypto/openpgp/errors"
|
||||
"crypto/openpgp/packet"
|
||||
"crypto/openpgp/s2k"
|
||||
"crypto/rand"
|
||||
|
@ -58,10 +58,10 @@ func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType p
|
|||
|
||||
func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType) (err error) {
|
||||
if signer.PrivateKey == nil {
|
||||
return error_.InvalidArgumentError("signing key doesn't have a private key")
|
||||
return errors.InvalidArgumentError("signing key doesn't have a private key")
|
||||
}
|
||||
if signer.PrivateKey.Encrypted {
|
||||
return error_.InvalidArgumentError("signing key is encrypted")
|
||||
return errors.InvalidArgumentError("signing key is encrypted")
|
||||
}
|
||||
|
||||
sig := new(packet.Signature)
|
||||
|
@ -77,7 +77,7 @@ func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.S
|
|||
}
|
||||
io.Copy(wrappedHash, message)
|
||||
|
||||
err = sig.Sign(h, signer.PrivateKey)
|
||||
err = sig.Sign(rand.Reader, h, signer.PrivateKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHi
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, packet.CipherAES128, key)
|
||||
w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, rand.Reader, packet.CipherAES128, key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint
|
|||
if signed != nil {
|
||||
signer = signed.signingKey().PrivateKey
|
||||
if signer == nil || signer.Encrypted {
|
||||
return nil, error_.InvalidArgumentError("signing key must be decrypted")
|
||||
return nil, errors.InvalidArgumentError("signing key must be decrypted")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,7 +183,7 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint
|
|||
for i := range to {
|
||||
encryptKeys[i] = to[i].encryptionKey()
|
||||
if encryptKeys[i].PublicKey == nil {
|
||||
return nil, error_.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys")
|
||||
return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys")
|
||||
}
|
||||
|
||||
sig := to[i].primaryIdentity().SelfSignature
|
||||
|
@ -201,7 +201,7 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint
|
|||
}
|
||||
|
||||
if len(candidateCiphers) == 0 || len(candidateHashes) == 0 {
|
||||
return nil, error_.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms")
|
||||
return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms")
|
||||
}
|
||||
|
||||
cipher := packet.CipherFunction(candidateCiphers[0])
|
||||
|
@ -217,7 +217,7 @@ func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHint
|
|||
}
|
||||
}
|
||||
|
||||
encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey)
|
||||
encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, rand.Reader, cipher, symKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ func (s signatureWriter) Close() error {
|
|||
IssuerKeyId: &s.signer.KeyId,
|
||||
}
|
||||
|
||||
if err := sig.Sign(s.h, s.signer); err != nil {
|
||||
if err := sig.Sign(rand.Reader, s.h, s.signer); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.literalData.Close(); err != nil {
|
||||
|
|
|
@ -222,7 +222,7 @@ func TestEncryption(t *testing.T) {
|
|||
|
||||
if test.isSigned {
|
||||
if md.SignatureError != nil {
|
||||
t.Errorf("#%d: signature error: %s", i, err)
|
||||
t.Errorf("#%d: signature error: %s", i, md.SignatureError)
|
||||
}
|
||||
if md.Signature == nil {
|
||||
t.Error("signature missing")
|
||||
|
|
|
@ -111,6 +111,18 @@ type ConnectionState struct {
|
|||
VerifiedChains [][]*x509.Certificate
|
||||
}
|
||||
|
||||
// ClientAuthType declares the policy the server will follow for
|
||||
// TLS Client Authentication.
|
||||
type ClientAuthType int
|
||||
|
||||
const (
|
||||
NoClientCert ClientAuthType = iota
|
||||
RequestClientCert
|
||||
RequireAnyClientCert
|
||||
VerifyClientCertIfGiven
|
||||
RequireAndVerifyClientCert
|
||||
)
|
||||
|
||||
// A Config structure is used to configure a TLS client or server. After one
|
||||
// has been passed to a TLS function it must not be modified.
|
||||
type Config struct {
|
||||
|
@ -120,7 +132,7 @@ type Config struct {
|
|||
Rand io.Reader
|
||||
|
||||
// Time returns the current time as the number of seconds since the epoch.
|
||||
// If Time is nil, TLS uses the system time.Seconds.
|
||||
// If Time is nil, TLS uses time.Now.
|
||||
Time func() time.Time
|
||||
|
||||
// Certificates contains one or more certificate chains
|
||||
|
@ -148,11 +160,14 @@ type Config struct {
|
|||
// hosting.
|
||||
ServerName string
|
||||
|
||||
// AuthenticateClient controls whether a server will request a certificate
|
||||
// from the client. It does not require that the client send a
|
||||
// certificate nor does it require that the certificate sent be
|
||||
// anything more than self-signed.
|
||||
AuthenticateClient bool
|
||||
// ClientAuth determines the server's policy for
|
||||
// TLS Client Authentication. The default is NoClientCert.
|
||||
ClientAuth ClientAuthType
|
||||
|
||||
// ClientCAs defines the set of root certificate authorities
|
||||
// that servers use if required to verify a client certificate
|
||||
// by the policy in ClientAuth.
|
||||
ClientCAs *x509.CertPool
|
||||
|
||||
// InsecureSkipVerify controls whether a client verifies the
|
||||
// server's certificate chain and host name.
|
||||
|
@ -259,6 +274,11 @@ type Certificate struct {
|
|||
// OCSPStaple contains an optional OCSP response which will be served
|
||||
// to clients that request it.
|
||||
OCSPStaple []byte
|
||||
// Leaf is the parsed form of the leaf certificate, which may be
|
||||
// initialized using x509.ParseCertificate to reduce per-handshake
|
||||
// processing for TLS clients doing client authentication. If nil, the
|
||||
// leaf certificate will be parsed as needed.
|
||||
Leaf *x509.Certificate
|
||||
}
|
||||
|
||||
// A TLS record.
|
||||
|
|
|
@ -31,7 +31,7 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
now := time.Seconds()
|
||||
now := time.Now()
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: new(big.Int).SetInt64(0),
|
||||
|
@ -39,8 +39,8 @@ func main() {
|
|||
CommonName: *hostName,
|
||||
Organization: []string{"Acme Co"},
|
||||
},
|
||||
NotBefore: time.SecondsToUTC(now - 300),
|
||||
NotAfter: time.SecondsToUTC(now + 60*60*24*365), // valid for 1 year.
|
||||
NotBefore: now.Add(-5 * time.Minute).UTC(),
|
||||
NotAfter: now.AddDate(1, 0, 0).UTC(), // valid for 1 year.
|
||||
|
||||
SubjectKeyId: []byte{1, 2, 3, 4},
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/subtle"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (c *Conn) clientHandshake() error {
|
||||
|
@ -162,10 +164,23 @@ func (c *Conn) clientHandshake() error {
|
|||
}
|
||||
}
|
||||
|
||||
transmitCert := false
|
||||
var certToSend *Certificate
|
||||
certReq, ok := msg.(*certificateRequestMsg)
|
||||
if ok {
|
||||
// We only accept certificates with RSA keys.
|
||||
// RFC 4346 on the certificateAuthorities field:
|
||||
// A list of the distinguished names of acceptable certificate
|
||||
// authorities. These distinguished names may specify a desired
|
||||
// distinguished name for a root CA or for a subordinate CA;
|
||||
// thus, this message can be used to describe both known roots
|
||||
// and a desired authorization space. If the
|
||||
// certificate_authorities list is empty then the client MAY
|
||||
// send any certificate of the appropriate
|
||||
// ClientCertificateType, unless there is some external
|
||||
// arrangement to the contrary.
|
||||
|
||||
finishedHash.Write(certReq.marshal())
|
||||
|
||||
// For now, we only know how to sign challenges with RSA
|
||||
rsaAvail := false
|
||||
for _, certType := range certReq.certificateTypes {
|
||||
if certType == certTypeRSASign {
|
||||
|
@ -174,23 +189,41 @@ func (c *Conn) clientHandshake() error {
|
|||
}
|
||||
}
|
||||
|
||||
// For now, only send a certificate back if the server gives us an
|
||||
// empty list of certificateAuthorities.
|
||||
//
|
||||
// RFC 4346 on the certificateAuthorities field:
|
||||
// A list of the distinguished names of acceptable certificate
|
||||
// authorities. These distinguished names may specify a desired
|
||||
// distinguished name for a root CA or for a subordinate CA; thus,
|
||||
// this message can be used to describe both known roots and a
|
||||
// desired authorization space. If the certificate_authorities
|
||||
// list is empty then the client MAY send any certificate of the
|
||||
// appropriate ClientCertificateType, unless there is some
|
||||
// external arrangement to the contrary.
|
||||
if rsaAvail && len(certReq.certificateAuthorities) == 0 {
|
||||
transmitCert = true
|
||||
}
|
||||
// We need to search our list of client certs for one
|
||||
// where SignatureAlgorithm is RSA and the Issuer is in
|
||||
// certReq.certificateAuthorities
|
||||
findCert:
|
||||
for i, cert := range c.config.Certificates {
|
||||
if !rsaAvail {
|
||||
continue
|
||||
}
|
||||
|
||||
finishedHash.Write(certReq.marshal())
|
||||
leaf := cert.Leaf
|
||||
if leaf == nil {
|
||||
if leaf, err = x509.ParseCertificate(cert.Certificate[0]); err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: failed to parse client certificate #" + strconv.Itoa(i) + ": " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if leaf.PublicKeyAlgorithm != x509.RSA {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(certReq.certificateAuthorities) == 0 {
|
||||
// they gave us an empty list, so just take the
|
||||
// first RSA cert from c.config.Certificates
|
||||
certToSend = &cert
|
||||
break
|
||||
}
|
||||
|
||||
for _, ca := range certReq.certificateAuthorities {
|
||||
if bytes.Equal(leaf.RawIssuer, ca) {
|
||||
certToSend = &cert
|
||||
break findCert
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg, err = c.readHandshake()
|
||||
if err != nil {
|
||||
|
@ -204,17 +237,9 @@ func (c *Conn) clientHandshake() error {
|
|||
}
|
||||
finishedHash.Write(shd.marshal())
|
||||
|
||||
var cert *x509.Certificate
|
||||
if transmitCert {
|
||||
if certToSend != nil {
|
||||
certMsg = new(certificateMsg)
|
||||
if len(c.config.Certificates) > 0 {
|
||||
cert, err = x509.ParseCertificate(c.config.Certificates[0].Certificate[0])
|
||||
if err == nil && cert.PublicKeyAlgorithm == x509.RSA {
|
||||
certMsg.certificates = c.config.Certificates[0].Certificate
|
||||
} else {
|
||||
cert = nil
|
||||
}
|
||||
}
|
||||
certMsg.certificates = certToSend.Certificate
|
||||
finishedHash.Write(certMsg.marshal())
|
||||
c.writeRecord(recordTypeHandshake, certMsg.marshal())
|
||||
}
|
||||
|
@ -229,7 +254,7 @@ func (c *Conn) clientHandshake() error {
|
|||
c.writeRecord(recordTypeHandshake, ckx.marshal())
|
||||
}
|
||||
|
||||
if cert != nil {
|
||||
if certToSend != nil {
|
||||
certVerify := new(certificateVerifyMsg)
|
||||
digest := make([]byte, 0, 36)
|
||||
digest = finishedHash.serverMD5.Sum(digest)
|
||||
|
|
|
@ -881,9 +881,11 @@ func (m *certificateRequestMsg) marshal() (x []byte) {
|
|||
|
||||
// See http://tools.ietf.org/html/rfc4346#section-7.4.4
|
||||
length := 1 + len(m.certificateTypes) + 2
|
||||
casLength := 0
|
||||
for _, ca := range m.certificateAuthorities {
|
||||
length += 2 + len(ca)
|
||||
casLength += 2 + len(ca)
|
||||
}
|
||||
length += casLength
|
||||
|
||||
x = make([]byte, 4+length)
|
||||
x[0] = typeCertificateRequest
|
||||
|
@ -895,10 +897,8 @@ func (m *certificateRequestMsg) marshal() (x []byte) {
|
|||
|
||||
copy(x[5:], m.certificateTypes)
|
||||
y := x[5+len(m.certificateTypes):]
|
||||
|
||||
numCA := len(m.certificateAuthorities)
|
||||
y[0] = uint8(numCA >> 8)
|
||||
y[1] = uint8(numCA)
|
||||
y[0] = uint8(casLength >> 8)
|
||||
y[1] = uint8(casLength)
|
||||
y = y[2:]
|
||||
for _, ca := range m.certificateAuthorities {
|
||||
y[0] = uint8(len(ca) >> 8)
|
||||
|
@ -909,7 +909,6 @@ func (m *certificateRequestMsg) marshal() (x []byte) {
|
|||
}
|
||||
|
||||
m.raw = x
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -937,31 +936,34 @@ func (m *certificateRequestMsg) unmarshal(data []byte) bool {
|
|||
}
|
||||
|
||||
data = data[numCertTypes:]
|
||||
|
||||
if len(data) < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
numCAs := uint16(data[0])<<16 | uint16(data[1])
|
||||
casLength := uint16(data[0])<<8 | uint16(data[1])
|
||||
data = data[2:]
|
||||
|
||||
m.certificateAuthorities = make([][]byte, numCAs)
|
||||
for i := uint16(0); i < numCAs; i++ {
|
||||
if len(data) < 2 {
|
||||
return false
|
||||
}
|
||||
caLen := uint16(data[0])<<16 | uint16(data[1])
|
||||
|
||||
data = data[2:]
|
||||
if len(data) < int(caLen) {
|
||||
return false
|
||||
}
|
||||
|
||||
ca := make([]byte, caLen)
|
||||
copy(ca, data)
|
||||
m.certificateAuthorities[i] = ca
|
||||
data = data[caLen:]
|
||||
if len(data) < int(casLength) {
|
||||
return false
|
||||
}
|
||||
cas := make([]byte, casLength)
|
||||
copy(cas, data)
|
||||
data = data[casLength:]
|
||||
|
||||
m.certificateAuthorities = nil
|
||||
for len(cas) > 0 {
|
||||
if len(cas) < 2 {
|
||||
return false
|
||||
}
|
||||
caLen := uint16(cas[0])<<8 | uint16(cas[1])
|
||||
cas = cas[2:]
|
||||
|
||||
if len(cas) < int(caLen) {
|
||||
return false
|
||||
}
|
||||
|
||||
m.certificateAuthorities = append(m.certificateAuthorities, cas[:caLen])
|
||||
cas = cas[caLen:]
|
||||
}
|
||||
if len(data) > 0 {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -150,14 +150,19 @@ FindCipherSuite:
|
|||
c.writeRecord(recordTypeHandshake, skx.marshal())
|
||||
}
|
||||
|
||||
if config.AuthenticateClient {
|
||||
if config.ClientAuth >= RequestClientCert {
|
||||
// Request a client certificate
|
||||
certReq := new(certificateRequestMsg)
|
||||
certReq.certificateTypes = []byte{certTypeRSASign}
|
||||
|
||||
// An empty list of certificateAuthorities signals to
|
||||
// the client that it may send any certificate in response
|
||||
// to our request.
|
||||
|
||||
// to our request. When we know the CAs we trust, then
|
||||
// we can send them down, so that the client can choose
|
||||
// an appropriate certificate to give to us.
|
||||
if config.ClientCAs != nil {
|
||||
certReq.certificateAuthorities = config.ClientCAs.Subjects()
|
||||
}
|
||||
finishedHash.Write(certReq.marshal())
|
||||
c.writeRecord(recordTypeHandshake, certReq.marshal())
|
||||
}
|
||||
|
@ -166,52 +171,87 @@ FindCipherSuite:
|
|||
finishedHash.Write(helloDone.marshal())
|
||||
c.writeRecord(recordTypeHandshake, helloDone.marshal())
|
||||
|
||||
var pub *rsa.PublicKey
|
||||
if config.AuthenticateClient {
|
||||
// Get client certificate
|
||||
msg, err = c.readHandshake()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certMsg, ok = msg.(*certificateMsg)
|
||||
if !ok {
|
||||
return c.sendAlert(alertUnexpectedMessage)
|
||||
}
|
||||
finishedHash.Write(certMsg.marshal())
|
||||
var pub *rsa.PublicKey // public key for client auth, if any
|
||||
|
||||
certs := make([]*x509.Certificate, len(certMsg.certificates))
|
||||
for i, asn1Data := range certMsg.certificates {
|
||||
cert, err := x509.ParseCertificate(asn1Data)
|
||||
if err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return errors.New("could not parse client's certificate: " + err.Error())
|
||||
}
|
||||
certs[i] = cert
|
||||
}
|
||||
|
||||
// TODO(agl): do better validation of certs: max path length, name restrictions etc.
|
||||
for i := 1; i < len(certs); i++ {
|
||||
if err := certs[i-1].CheckSignatureFrom(certs[i]); err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return errors.New("could not validate certificate signature: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(certs) > 0 {
|
||||
key, ok := certs[0].PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return c.sendAlert(alertUnsupportedCertificate)
|
||||
}
|
||||
pub = key
|
||||
c.peerCertificates = certs
|
||||
}
|
||||
}
|
||||
|
||||
// Get client key exchange
|
||||
msg, err = c.readHandshake()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we requested a client certificate, then the client must send a
|
||||
// certificate message, even if it's empty.
|
||||
if config.ClientAuth >= RequestClientCert {
|
||||
if certMsg, ok = msg.(*certificateMsg); !ok {
|
||||
return c.sendAlert(alertHandshakeFailure)
|
||||
}
|
||||
finishedHash.Write(certMsg.marshal())
|
||||
|
||||
if len(certMsg.certificates) == 0 {
|
||||
// The client didn't actually send a certificate
|
||||
switch config.ClientAuth {
|
||||
case RequireAnyClientCert, RequireAndVerifyClientCert:
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return errors.New("tls: client didn't provide a certificate")
|
||||
}
|
||||
}
|
||||
|
||||
certs := make([]*x509.Certificate, len(certMsg.certificates))
|
||||
for i, asn1Data := range certMsg.certificates {
|
||||
if certs[i], err = x509.ParseCertificate(asn1Data); err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return errors.New("tls: failed to parse client certificate: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if c.config.ClientAuth >= VerifyClientCertIfGiven && len(certs) > 0 {
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: c.config.ClientCAs,
|
||||
CurrentTime: c.config.time(),
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
|
||||
for i, cert := range certs {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
|
||||
chains, err := certs[0].Verify(opts)
|
||||
if err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return errors.New("tls: failed to verify client's certificate: " + err.Error())
|
||||
}
|
||||
|
||||
ok := false
|
||||
for _, ku := range certs[0].ExtKeyUsage {
|
||||
if ku == x509.ExtKeyUsageClientAuth {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
c.sendAlert(alertHandshakeFailure)
|
||||
return errors.New("tls: client's certificate's extended key usage doesn't permit it to be used for client authentication")
|
||||
}
|
||||
|
||||
c.verifiedChains = chains
|
||||
}
|
||||
|
||||
if len(certs) > 0 {
|
||||
if pub, ok = certs[0].PublicKey.(*rsa.PublicKey); !ok {
|
||||
return c.sendAlert(alertUnsupportedCertificate)
|
||||
}
|
||||
c.peerCertificates = certs
|
||||
}
|
||||
|
||||
msg, err = c.readHandshake()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Get client key exchange
|
||||
ckx, ok := msg.(*clientKeyExchangeMsg)
|
||||
if !ok {
|
||||
return c.sendAlert(alertUnexpectedMessage)
|
||||
|
|
|
@ -7,9 +7,12 @@ package tls
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"io"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"strconv"
|
||||
|
@ -109,16 +112,18 @@ func TestClose(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testServerScript(t *testing.T, name string, serverScript [][]byte, config *Config) {
|
||||
func testServerScript(t *testing.T, name string, serverScript [][]byte, config *Config, peers []*x509.Certificate) {
|
||||
c, s := net.Pipe()
|
||||
srv := Server(s, config)
|
||||
pchan := make(chan []*x509.Certificate, 1)
|
||||
go func() {
|
||||
srv.Write([]byte("hello, world\n"))
|
||||
srv.Close()
|
||||
s.Close()
|
||||
st := srv.ConnectionState()
|
||||
pchan <- st.PeerCertificates
|
||||
}()
|
||||
|
||||
defer c.Close()
|
||||
for i, b := range serverScript {
|
||||
if i%2 == 0 {
|
||||
c.Write(b)
|
||||
|
@ -133,34 +138,66 @@ func testServerScript(t *testing.T, name string, serverScript [][]byte, config *
|
|||
t.Fatalf("%s #%d: mismatch on read: got:%x want:%x", name, i, bb, b)
|
||||
}
|
||||
}
|
||||
c.Close()
|
||||
|
||||
if peers != nil {
|
||||
gotpeers := <-pchan
|
||||
if len(peers) == len(gotpeers) {
|
||||
for i, _ := range peers {
|
||||
if !peers[i].Equal(gotpeers[i]) {
|
||||
t.Fatalf("%s: mismatch on peer cert %d", name, i)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("%s: mismatch on peer list length: %d (wanted) != %d (got)", name, len(peers), len(gotpeers))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeServerRC4(t *testing.T) {
|
||||
testServerScript(t, "RC4", rc4ServerScript, testConfig)
|
||||
testServerScript(t, "RC4", rc4ServerScript, testConfig, nil)
|
||||
}
|
||||
|
||||
func TestHandshakeServer3DES(t *testing.T) {
|
||||
des3Config := new(Config)
|
||||
*des3Config = *testConfig
|
||||
des3Config.CipherSuites = []uint16{TLS_RSA_WITH_3DES_EDE_CBC_SHA}
|
||||
testServerScript(t, "3DES", des3ServerScript, des3Config)
|
||||
testServerScript(t, "3DES", des3ServerScript, des3Config, nil)
|
||||
}
|
||||
|
||||
func TestHandshakeServerAES(t *testing.T) {
|
||||
aesConfig := new(Config)
|
||||
*aesConfig = *testConfig
|
||||
aesConfig.CipherSuites = []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}
|
||||
testServerScript(t, "AES", aesServerScript, aesConfig)
|
||||
testServerScript(t, "AES", aesServerScript, aesConfig, nil)
|
||||
}
|
||||
|
||||
func TestHandshakeServerSSLv3(t *testing.T) {
|
||||
testServerScript(t, "SSLv3", sslv3ServerScript, testConfig)
|
||||
testServerScript(t, "SSLv3", sslv3ServerScript, testConfig, nil)
|
||||
}
|
||||
|
||||
type clientauthTest struct {
|
||||
name string
|
||||
clientauth ClientAuthType
|
||||
peers []*x509.Certificate
|
||||
script [][]byte
|
||||
}
|
||||
|
||||
func TestClientAuth(t *testing.T) {
|
||||
for _, cat := range clientauthTests {
|
||||
t.Log("running", cat.name)
|
||||
cfg := new(Config)
|
||||
*cfg = *testConfig
|
||||
cfg.ClientAuth = cat.clientauth
|
||||
testServerScript(t, cat.name, cat.script, cfg, cat.peers)
|
||||
}
|
||||
}
|
||||
|
||||
var serve = flag.Bool("serve", false, "run a TLS server on :10443")
|
||||
var testCipherSuites = flag.String("ciphersuites",
|
||||
"0x"+strconv.FormatInt(int64(TLS_RSA_WITH_RC4_128_SHA), 16),
|
||||
"cipher suites to accept in serving mode")
|
||||
var testClientAuth = flag.Int("clientauth", 0, "value for tls.Config.ClientAuth")
|
||||
|
||||
func TestRunServer(t *testing.T) {
|
||||
if !*serve {
|
||||
|
@ -177,6 +214,8 @@ func TestRunServer(t *testing.T) {
|
|||
testConfig.CipherSuites[i] = uint16(suite)
|
||||
}
|
||||
|
||||
testConfig.ClientAuth = ClientAuthType(*testClientAuth)
|
||||
|
||||
l, err := Listen("tcp", ":10443", testConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -185,13 +224,23 @@ func TestRunServer(t *testing.T) {
|
|||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.Printf("error from TLS handshake: %s", err)
|
||||
break
|
||||
}
|
||||
|
||||
_, err = c.Write([]byte("hello, world\n"))
|
||||
if err != nil {
|
||||
t.Errorf("error from TLS: %s", err)
|
||||
break
|
||||
log.Printf("error from TLS: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
st := c.(*Conn).ConnectionState()
|
||||
if len(st.PeerCertificates) > 0 {
|
||||
log.Print("Handling request from client ", st.PeerCertificates[0].Subject.CommonName)
|
||||
} else {
|
||||
log.Print("Handling request from anon client")
|
||||
}
|
||||
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
@ -221,6 +270,18 @@ var testPrivateKey = &rsa.PrivateKey{
|
|||
},
|
||||
}
|
||||
|
||||
func loadPEMCert(in string) *x509.Certificate {
|
||||
block, _ := pem.Decode([]byte(in))
|
||||
if block.Type == "CERTIFICATE" && len(block.Headers) == 0 {
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err == nil {
|
||||
return cert
|
||||
}
|
||||
panic("error parsing cert")
|
||||
}
|
||||
panic("error parsing PEM")
|
||||
}
|
||||
|
||||
// Script of interaction with gnutls implementation.
|
||||
// The values for this test are obtained by building and running in server mode:
|
||||
// % gotest -test.run "TestRunServer" -serve
|
||||
|
@ -229,23 +290,22 @@ var testPrivateKey = &rsa.PrivateKey{
|
|||
// % python parse-gnutls-cli-debug-log.py < /tmp/log
|
||||
var rc4ServerScript = [][]byte{
|
||||
{
|
||||
0x16, 0x03, 0x02, 0x00, 0x7f, 0x01, 0x00, 0x00,
|
||||
0x7b, 0x03, 0x02, 0x4d, 0x08, 0x1f, 0x5a, 0x7a,
|
||||
0x0a, 0x92, 0x2f, 0xf0, 0x73, 0x16, 0x3a, 0x88,
|
||||
0x14, 0x85, 0x4c, 0x98, 0x15, 0x7b, 0x65, 0xe0,
|
||||
0x78, 0xd0, 0xed, 0xd0, 0xf3, 0x65, 0x20, 0xeb,
|
||||
0x80, 0xd1, 0x0b, 0x00, 0x00, 0x34, 0x00, 0x33,
|
||||
0x16, 0x03, 0x02, 0x00, 0x7a, 0x01, 0x00, 0x00,
|
||||
0x76, 0x03, 0x02, 0x4e, 0xdd, 0xe6, 0xa5, 0xf7,
|
||||
0x00, 0x36, 0xf7, 0x83, 0xec, 0x93, 0x7c, 0xd2,
|
||||
0x4d, 0xe7, 0x7b, 0xf5, 0x4c, 0xf7, 0xe3, 0x86,
|
||||
0xe8, 0xec, 0x3b, 0xbd, 0x2c, 0x9a, 0x3f, 0x57,
|
||||
0xf0, 0xa4, 0xd4, 0x00, 0x00, 0x34, 0x00, 0x33,
|
||||
0x00, 0x45, 0x00, 0x39, 0x00, 0x88, 0x00, 0x16,
|
||||
0x00, 0x32, 0x00, 0x44, 0x00, 0x38, 0x00, 0x87,
|
||||
0x00, 0x13, 0x00, 0x66, 0x00, 0x90, 0x00, 0x91,
|
||||
0x00, 0x8f, 0x00, 0x8e, 0x00, 0x2f, 0x00, 0x41,
|
||||
0x00, 0x35, 0x00, 0x84, 0x00, 0x0a, 0x00, 0x05,
|
||||
0x00, 0x04, 0x00, 0x8c, 0x00, 0x8d, 0x00, 0x8b,
|
||||
0x00, 0x8a, 0x01, 0x00, 0x00, 0x1e, 0x00, 0x09,
|
||||
0x00, 0x8a, 0x01, 0x00, 0x00, 0x19, 0x00, 0x09,
|
||||
0x00, 0x03, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x0e, 0x00, 0x0c, 0x00, 0x00, 0x09, 0x6c, 0x6f,
|
||||
0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0xff,
|
||||
0x01, 0x00, 0x01, 0x00,
|
||||
0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -349,38 +409,46 @@ var rc4ServerScript = [][]byte{
|
|||
|
||||
{
|
||||
0x16, 0x03, 0x01, 0x00, 0x86, 0x10, 0x00, 0x00,
|
||||
0x82, 0x00, 0x80, 0x3c, 0x13, 0xd7, 0x12, 0xc1,
|
||||
0x6a, 0xf0, 0x3f, 0x8c, 0xa1, 0x35, 0x5d, 0xc5,
|
||||
0x89, 0x1e, 0x9e, 0xcd, 0x32, 0xc7, 0x9e, 0xe6,
|
||||
0xae, 0xd5, 0xf1, 0xbf, 0x70, 0xd7, 0xa9, 0xef,
|
||||
0x2c, 0x4c, 0xf4, 0x22, 0xbc, 0x17, 0x17, 0xaa,
|
||||
0x05, 0xf3, 0x9f, 0x80, 0xf2, 0xe9, 0x82, 0x2f,
|
||||
0x2a, 0x15, 0x54, 0x0d, 0x16, 0x0e, 0x77, 0x4c,
|
||||
0x28, 0x3c, 0x03, 0x2d, 0x2d, 0xd7, 0xc8, 0x64,
|
||||
0xd9, 0x59, 0x4b, 0x1c, 0xf4, 0xde, 0xff, 0x2f,
|
||||
0xbc, 0x94, 0xaf, 0x18, 0x26, 0x37, 0xce, 0x4f,
|
||||
0x84, 0x74, 0x2e, 0x45, 0x66, 0x7c, 0x0c, 0x54,
|
||||
0x46, 0x36, 0x5f, 0x65, 0x21, 0x7b, 0x83, 0x8c,
|
||||
0x6d, 0x76, 0xcd, 0x0d, 0x9f, 0xda, 0x1c, 0xa4,
|
||||
0x6e, 0xfe, 0xb1, 0xf7, 0x09, 0x0d, 0xfb, 0x74,
|
||||
0x66, 0x34, 0x99, 0x89, 0x7f, 0x5f, 0x77, 0x87,
|
||||
0x4a, 0x66, 0x4b, 0xa9, 0x59, 0x57, 0xe3, 0x56,
|
||||
0x0d, 0xdd, 0xd8, 0x14, 0x03, 0x01, 0x00, 0x01,
|
||||
0x01, 0x16, 0x03, 0x01, 0x00, 0x24, 0xc0, 0x4e,
|
||||
0xd3, 0x0f, 0xb5, 0xc0, 0x57, 0xa6, 0x18, 0x80,
|
||||
0x80, 0x6b, 0x49, 0xfe, 0xbd, 0x3a, 0x7a, 0x2c,
|
||||
0xef, 0x70, 0xb5, 0x1c, 0xd2, 0xdf, 0x5f, 0x78,
|
||||
0x5a, 0xd8, 0x4f, 0xa0, 0x95, 0xb4, 0xb3, 0xb5,
|
||||
0xaa, 0x3b,
|
||||
0x82, 0x00, 0x80, 0x39, 0xe2, 0x0f, 0x49, 0xa0,
|
||||
0xe6, 0xe4, 0x3b, 0x0c, 0x5f, 0xce, 0x39, 0x97,
|
||||
0x6c, 0xb6, 0x41, 0xd9, 0xe1, 0x52, 0x8f, 0x43,
|
||||
0xb3, 0xc6, 0x4f, 0x9a, 0xe2, 0x1e, 0xb9, 0x3b,
|
||||
0xe3, 0x72, 0x17, 0x68, 0xb2, 0x0d, 0x7b, 0x71,
|
||||
0x33, 0x96, 0x5c, 0xf9, 0xfe, 0x18, 0x8f, 0x2f,
|
||||
0x2b, 0x82, 0xec, 0x03, 0xf2, 0x16, 0xa8, 0xf8,
|
||||
0x39, 0xf9, 0xbb, 0x5a, 0xd3, 0x0c, 0xc1, 0x2a,
|
||||
0x52, 0xa1, 0x90, 0x20, 0x6b, 0x24, 0xc9, 0x55,
|
||||
0xee, 0x05, 0xd8, 0xb3, 0x43, 0x58, 0xf6, 0x7f,
|
||||
0x68, 0x2d, 0xb3, 0xd1, 0x1b, 0x30, 0xaa, 0xdf,
|
||||
0xfc, 0x85, 0xf1, 0xab, 0x14, 0x51, 0x91, 0x78,
|
||||
0x29, 0x35, 0x65, 0xe0, 0x9c, 0xf6, 0xb7, 0x35,
|
||||
0x33, 0xdb, 0x28, 0x93, 0x4d, 0x86, 0xbc, 0xfe,
|
||||
0xaa, 0xd1, 0xc0, 0x2e, 0x4d, 0xec, 0xa2, 0x98,
|
||||
0xca, 0x08, 0xb2, 0x91, 0x14, 0xde, 0x97, 0x3a,
|
||||
0xc4, 0x6b, 0x49, 0x14, 0x03, 0x01, 0x00, 0x01,
|
||||
0x01, 0x16, 0x03, 0x01, 0x00, 0x24, 0x7a, 0xcb,
|
||||
0x3b, 0x0e, 0xbb, 0x7a, 0x56, 0x39, 0xaf, 0x83,
|
||||
0xae, 0xfd, 0x25, 0xfd, 0x64, 0xb4, 0x0c, 0x0c,
|
||||
0x17, 0x46, 0x54, 0x2c, 0x6a, 0x07, 0x83, 0xc6,
|
||||
0x46, 0x08, 0x0b, 0xcd, 0x15, 0x53, 0xef, 0x40,
|
||||
0x4e, 0x56,
|
||||
},
|
||||
|
||||
{
|
||||
0x14, 0x03, 0x01, 0x00, 0x01, 0x01, 0x16, 0x03,
|
||||
0x01, 0x00, 0x24, 0x9d, 0xc9, 0xda, 0xdf, 0xeb,
|
||||
0xc8, 0xdb, 0xf8, 0x94, 0xa5, 0xef, 0xd5, 0xfc,
|
||||
0x89, 0x01, 0x64, 0x30, 0x77, 0x5a, 0x18, 0x4b,
|
||||
0x16, 0x79, 0x9c, 0xf6, 0xf5, 0x09, 0x22, 0x12,
|
||||
0x4c, 0x3e, 0xa8, 0x8e, 0x91, 0xa5, 0x24,
|
||||
0x01, 0x00, 0x24, 0xd3, 0x72, 0xeb, 0x29, 0xb9,
|
||||
0x15, 0x29, 0xb5, 0xe5, 0xb7, 0xef, 0x5c, 0xb2,
|
||||
0x9d, 0xf6, 0xc8, 0x47, 0xd6, 0xa0, 0x84, 0xf0,
|
||||
0x8c, 0xcb, 0xe6, 0xbe, 0xbc, 0xfb, 0x38, 0x90,
|
||||
0x89, 0x60, 0xa2, 0xe8, 0xaa, 0xb3, 0x12, 0x17,
|
||||
0x03, 0x01, 0x00, 0x21, 0x67, 0x4a, 0x3d, 0x31,
|
||||
0x6c, 0x5a, 0x1c, 0xf9, 0x6e, 0xf1, 0xd8, 0x12,
|
||||
0x0e, 0xb9, 0xfd, 0xfc, 0x66, 0x91, 0xd1, 0x1d,
|
||||
0x6e, 0xe4, 0x55, 0xdd, 0x11, 0xb9, 0xb8, 0xa2,
|
||||
0x65, 0xa1, 0x95, 0x64, 0x1c, 0x15, 0x03, 0x01,
|
||||
0x00, 0x16, 0x9b, 0xa0, 0x24, 0xe3, 0xcb, 0xae,
|
||||
0xad, 0x51, 0xb3, 0x63, 0x59, 0x78, 0x49, 0x24,
|
||||
0x06, 0x6e, 0xee, 0x7a, 0xd7, 0x74, 0x53, 0x04,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -878,3 +946,625 @@ var sslv3ServerScript = [][]byte{
|
|||
0xaf, 0xd3, 0xb7, 0xa3, 0xcc, 0x4a, 0x1d, 0x2e,
|
||||
},
|
||||
}
|
||||
|
||||
var clientauthTests = []clientauthTest{
|
||||
// Server doesn't asks for cert
|
||||
// gotest -test.run "TestRunServer" -serve -clientauth 0
|
||||
// gnutls-cli --insecure --debug 100 -p 10443 localhost 2>&1 |
|
||||
// python parse-gnutls-cli-debug-log.py
|
||||
{"NoClientCert", NoClientCert, nil,
|
||||
[][]byte{{
|
||||
0x16, 0x03, 0x02, 0x00, 0x7a, 0x01, 0x00, 0x00,
|
||||
0x76, 0x03, 0x02, 0x4e, 0xe0, 0x92, 0x5d, 0xcd,
|
||||
0xfe, 0x0c, 0x69, 0xd4, 0x7d, 0x8e, 0xa6, 0x88,
|
||||
0xde, 0x72, 0x04, 0x29, 0x6a, 0x4a, 0x16, 0x23,
|
||||
0xd7, 0x8f, 0xbc, 0xfa, 0x80, 0x73, 0x2e, 0x12,
|
||||
0xb7, 0x0b, 0x39, 0x00, 0x00, 0x34, 0x00, 0x33,
|
||||
0x00, 0x45, 0x00, 0x39, 0x00, 0x88, 0x00, 0x16,
|
||||
0x00, 0x32, 0x00, 0x44, 0x00, 0x38, 0x00, 0x87,
|
||||
0x00, 0x13, 0x00, 0x66, 0x00, 0x90, 0x00, 0x91,
|
||||
0x00, 0x8f, 0x00, 0x8e, 0x00, 0x2f, 0x00, 0x41,
|
||||
0x00, 0x35, 0x00, 0x84, 0x00, 0x0a, 0x00, 0x05,
|
||||
0x00, 0x04, 0x00, 0x8c, 0x00, 0x8d, 0x00, 0x8b,
|
||||
0x00, 0x8a, 0x01, 0x00, 0x00, 0x19, 0x00, 0x09,
|
||||
0x00, 0x03, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x0e, 0x00, 0x0c, 0x00, 0x00, 0x09, 0x6c, 0x6f,
|
||||
0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
|
||||
},
|
||||
|
||||
{
|
||||
0x16, 0x03, 0x01, 0x00, 0x2a, 0x02, 0x00, 0x00,
|
||||
0x26, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x16,
|
||||
0x03, 0x01, 0x02, 0xbe, 0x0b, 0x00, 0x02, 0xba,
|
||||
0x00, 0x02, 0xb7, 0x00, 0x02, 0xb4, 0x30, 0x82,
|
||||
0x02, 0xb0, 0x30, 0x82, 0x02, 0x19, 0xa0, 0x03,
|
||||
0x02, 0x01, 0x02, 0x02, 0x09, 0x00, 0x85, 0xb0,
|
||||
0xbb, 0xa4, 0x8a, 0x7f, 0xb8, 0xca, 0x30, 0x0d,
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
|
||||
0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x45, 0x31,
|
||||
0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
|
||||
0x13, 0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11,
|
||||
0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x53,
|
||||
0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74,
|
||||
0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55,
|
||||
0x04, 0x0a, 0x13, 0x18, 0x49, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64,
|
||||
0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79,
|
||||
0x20, 0x4c, 0x74, 0x64, 0x30, 0x1e, 0x17, 0x0d,
|
||||
0x31, 0x30, 0x30, 0x34, 0x32, 0x34, 0x30, 0x39,
|
||||
0x30, 0x39, 0x33, 0x38, 0x5a, 0x17, 0x0d, 0x31,
|
||||
0x31, 0x30, 0x34, 0x32, 0x34, 0x30, 0x39, 0x30,
|
||||
0x39, 0x33, 0x38, 0x5a, 0x30, 0x45, 0x31, 0x0b,
|
||||
0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
|
||||
0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06,
|
||||
0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x53, 0x6f,
|
||||
0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65,
|
||||
0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04,
|
||||
0x0a, 0x13, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67,
|
||||
0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20,
|
||||
0x4c, 0x74, 0x64, 0x30, 0x81, 0x9f, 0x30, 0x0d,
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
|
||||
0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d,
|
||||
0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00,
|
||||
0xbb, 0x79, 0xd6, 0xf5, 0x17, 0xb5, 0xe5, 0xbf,
|
||||
0x46, 0x10, 0xd0, 0xdc, 0x69, 0xbe, 0xe6, 0x2b,
|
||||
0x07, 0x43, 0x5a, 0xd0, 0x03, 0x2d, 0x8a, 0x7a,
|
||||
0x43, 0x85, 0xb7, 0x14, 0x52, 0xe7, 0xa5, 0x65,
|
||||
0x4c, 0x2c, 0x78, 0xb8, 0x23, 0x8c, 0xb5, 0xb4,
|
||||
0x82, 0xe5, 0xde, 0x1f, 0x95, 0x3b, 0x7e, 0x62,
|
||||
0xa5, 0x2c, 0xa5, 0x33, 0xd6, 0xfe, 0x12, 0x5c,
|
||||
0x7a, 0x56, 0xfc, 0xf5, 0x06, 0xbf, 0xfa, 0x58,
|
||||
0x7b, 0x26, 0x3f, 0xb5, 0xcd, 0x04, 0xd3, 0xd0,
|
||||
0xc9, 0x21, 0x96, 0x4a, 0xc7, 0xf4, 0x54, 0x9f,
|
||||
0x5a, 0xbf, 0xef, 0x42, 0x71, 0x00, 0xfe, 0x18,
|
||||
0x99, 0x07, 0x7f, 0x7e, 0x88, 0x7d, 0x7d, 0xf1,
|
||||
0x04, 0x39, 0xc4, 0xa2, 0x2e, 0xdb, 0x51, 0xc9,
|
||||
0x7c, 0xe3, 0xc0, 0x4c, 0x3b, 0x32, 0x66, 0x01,
|
||||
0xcf, 0xaf, 0xb1, 0x1d, 0xb8, 0x71, 0x9a, 0x1d,
|
||||
0xdb, 0xdb, 0x89, 0x6b, 0xae, 0xda, 0x2d, 0x79,
|
||||
0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xa7,
|
||||
0x30, 0x81, 0xa4, 0x30, 0x1d, 0x06, 0x03, 0x55,
|
||||
0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb1, 0xad,
|
||||
0xe2, 0x85, 0x5a, 0xcf, 0xcb, 0x28, 0xdb, 0x69,
|
||||
0xce, 0x23, 0x69, 0xde, 0xd3, 0x26, 0x8e, 0x18,
|
||||
0x88, 0x39, 0x30, 0x75, 0x06, 0x03, 0x55, 0x1d,
|
||||
0x23, 0x04, 0x6e, 0x30, 0x6c, 0x80, 0x14, 0xb1,
|
||||
0xad, 0xe2, 0x85, 0x5a, 0xcf, 0xcb, 0x28, 0xdb,
|
||||
0x69, 0xce, 0x23, 0x69, 0xde, 0xd3, 0x26, 0x8e,
|
||||
0x18, 0x88, 0x39, 0xa1, 0x49, 0xa4, 0x47, 0x30,
|
||||
0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
|
||||
0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x13,
|
||||
0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
|
||||
0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06,
|
||||
0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x49, 0x6e,
|
||||
0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57,
|
||||
0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50,
|
||||
0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x82, 0x09,
|
||||
0x00, 0x85, 0xb0, 0xbb, 0xa4, 0x8a, 0x7f, 0xb8,
|
||||
0xca, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13,
|
||||
0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30,
|
||||
0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
|
||||
0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81,
|
||||
0x81, 0x00, 0x08, 0x6c, 0x45, 0x24, 0xc7, 0x6b,
|
||||
0xb1, 0x59, 0xab, 0x0c, 0x52, 0xcc, 0xf2, 0xb0,
|
||||
0x14, 0xd7, 0x87, 0x9d, 0x7a, 0x64, 0x75, 0xb5,
|
||||
0x5a, 0x95, 0x66, 0xe4, 0xc5, 0x2b, 0x8e, 0xae,
|
||||
0x12, 0x66, 0x1f, 0xeb, 0x4f, 0x38, 0xb3, 0x6e,
|
||||
0x60, 0xd3, 0x92, 0xfd, 0xf7, 0x41, 0x08, 0xb5,
|
||||
0x25, 0x13, 0xb1, 0x18, 0x7a, 0x24, 0xfb, 0x30,
|
||||
0x1d, 0xba, 0xed, 0x98, 0xb9, 0x17, 0xec, 0xe7,
|
||||
0xd7, 0x31, 0x59, 0xdb, 0x95, 0xd3, 0x1d, 0x78,
|
||||
0xea, 0x50, 0x56, 0x5c, 0xd5, 0x82, 0x5a, 0x2d,
|
||||
0x5a, 0x5f, 0x33, 0xc4, 0xb6, 0xd8, 0xc9, 0x75,
|
||||
0x90, 0x96, 0x8c, 0x0f, 0x52, 0x98, 0xb5, 0xcd,
|
||||
0x98, 0x1f, 0x89, 0x20, 0x5f, 0xf2, 0xa0, 0x1c,
|
||||
0xa3, 0x1b, 0x96, 0x94, 0xdd, 0xa9, 0xfd, 0x57,
|
||||
0xe9, 0x70, 0xe8, 0x26, 0x6d, 0x71, 0x99, 0x9b,
|
||||
0x26, 0x6e, 0x38, 0x50, 0x29, 0x6c, 0x90, 0xa7,
|
||||
0xbd, 0xd9, 0x16, 0x03, 0x01, 0x00, 0x04, 0x0e,
|
||||
0x00, 0x00, 0x00,
|
||||
},
|
||||
|
||||
{
|
||||
0x16, 0x03, 0x01, 0x00, 0x86, 0x10, 0x00, 0x00,
|
||||
0x82, 0x00, 0x80, 0x10, 0xe1, 0x00, 0x3d, 0x0a,
|
||||
0x6b, 0x02, 0x7f, 0x97, 0xde, 0xfb, 0x65, 0x46,
|
||||
0x1a, 0x50, 0x4e, 0x34, 0x9a, 0xae, 0x14, 0x7e,
|
||||
0xec, 0xef, 0x85, 0x15, 0x3b, 0x39, 0xc2, 0x45,
|
||||
0x04, 0x40, 0x92, 0x71, 0xd6, 0x7e, 0xf6, 0xfd,
|
||||
0x4d, 0x84, 0xf7, 0xc4, 0x77, 0x99, 0x3d, 0xe2,
|
||||
0xc3, 0x8d, 0xb0, 0x4c, 0x74, 0xc8, 0x51, 0xec,
|
||||
0xb2, 0xe8, 0x6b, 0xa1, 0xd2, 0x4d, 0xd8, 0x61,
|
||||
0x92, 0x7a, 0x24, 0x57, 0x44, 0x4f, 0xa2, 0x1e,
|
||||
0x74, 0x0b, 0x06, 0x4b, 0x80, 0x34, 0x8b, 0xfe,
|
||||
0xc2, 0x0e, 0xc1, 0xcd, 0xab, 0x0c, 0x3f, 0x54,
|
||||
0xe2, 0x44, 0xe9, 0x6c, 0x2b, 0xba, 0x7b, 0x64,
|
||||
0xf1, 0x93, 0x65, 0x75, 0xf2, 0x35, 0xff, 0x27,
|
||||
0x03, 0xd5, 0x64, 0xe6, 0x8e, 0xe7, 0x7b, 0x56,
|
||||
0xb6, 0x61, 0x73, 0xeb, 0xa2, 0xdc, 0xa4, 0x6e,
|
||||
0x52, 0xac, 0xbc, 0xba, 0x11, 0xa3, 0xd2, 0x61,
|
||||
0x4a, 0xe0, 0xbb, 0x14, 0x03, 0x01, 0x00, 0x01,
|
||||
0x01, 0x16, 0x03, 0x01, 0x00, 0x24, 0xd2, 0x5a,
|
||||
0x0c, 0x2a, 0x27, 0x96, 0xba, 0xa9, 0x67, 0xd2,
|
||||
0x51, 0x68, 0x32, 0x68, 0x22, 0x1f, 0xb9, 0x27,
|
||||
0x79, 0x59, 0x28, 0xdf, 0x38, 0x1f, 0x92, 0x21,
|
||||
0x5d, 0x0f, 0xf4, 0xc0, 0xee, 0xb7, 0x10, 0x5a,
|
||||
0xa9, 0x45,
|
||||
},
|
||||
|
||||
{
|
||||
0x14, 0x03, 0x01, 0x00, 0x01, 0x01, 0x16, 0x03,
|
||||
0x01, 0x00, 0x24, 0x13, 0x6f, 0x6c, 0x71, 0x83,
|
||||
0x59, 0xcf, 0x32, 0x72, 0xe9, 0xce, 0xcc, 0x7a,
|
||||
0x6c, 0xf0, 0x72, 0x39, 0x16, 0xae, 0x40, 0x61,
|
||||
0xfa, 0x92, 0x4c, 0xe7, 0xf2, 0x1a, 0xd7, 0x0c,
|
||||
0x84, 0x76, 0x6c, 0xe9, 0x11, 0x43, 0x19, 0x17,
|
||||
0x03, 0x01, 0x00, 0x21, 0xc0, 0xa2, 0x13, 0x28,
|
||||
0x94, 0x8c, 0x5c, 0xd6, 0x79, 0xb9, 0xfe, 0xae,
|
||||
0x45, 0x4b, 0xc0, 0x7c, 0xae, 0x2d, 0xb4, 0x0d,
|
||||
0x31, 0xc4, 0xad, 0x22, 0xd7, 0x1e, 0x99, 0x1c,
|
||||
0x4c, 0x69, 0xab, 0x42, 0x61, 0x15, 0x03, 0x01,
|
||||
0x00, 0x16, 0xe1, 0x0c, 0x67, 0xf3, 0xf4, 0xb9,
|
||||
0x8e, 0x81, 0x8e, 0x01, 0xb8, 0xa0, 0x69, 0x8c,
|
||||
0x03, 0x11, 0x43, 0x3e, 0xee, 0xb7, 0x4d, 0x69,
|
||||
}}},
|
||||
// Server asks for cert with empty CA list, client doesn't give it.
|
||||
// gotest -test.run "TestRunServer" -serve -clientauth 1
|
||||
// gnutls-cli --insecure --debug 100 -p 10443 localhost
|
||||
{"RequestClientCert, none given", RequestClientCert, nil,
|
||||
[][]byte{{
|
||||
0x16, 0x03, 0x02, 0x00, 0x7a, 0x01, 0x00, 0x00,
|
||||
0x76, 0x03, 0x02, 0x4e, 0xe0, 0x93, 0xe2, 0x47,
|
||||
0x06, 0xa0, 0x61, 0x0c, 0x51, 0xdd, 0xf0, 0xef,
|
||||
0xf4, 0x30, 0x72, 0xe1, 0xa6, 0x50, 0x68, 0x82,
|
||||
0x3c, 0xfb, 0xcb, 0x72, 0x5e, 0x73, 0x9d, 0xda,
|
||||
0x27, 0x35, 0x72, 0x00, 0x00, 0x34, 0x00, 0x33,
|
||||
0x00, 0x45, 0x00, 0x39, 0x00, 0x88, 0x00, 0x16,
|
||||
0x00, 0x32, 0x00, 0x44, 0x00, 0x38, 0x00, 0x87,
|
||||
0x00, 0x13, 0x00, 0x66, 0x00, 0x90, 0x00, 0x91,
|
||||
0x00, 0x8f, 0x00, 0x8e, 0x00, 0x2f, 0x00, 0x41,
|
||||
0x00, 0x35, 0x00, 0x84, 0x00, 0x0a, 0x00, 0x05,
|
||||
0x00, 0x04, 0x00, 0x8c, 0x00, 0x8d, 0x00, 0x8b,
|
||||
0x00, 0x8a, 0x01, 0x00, 0x00, 0x19, 0x00, 0x09,
|
||||
0x00, 0x03, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x0e, 0x00, 0x0c, 0x00, 0x00, 0x09, 0x6c, 0x6f,
|
||||
0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
|
||||
},
|
||||
|
||||
{
|
||||
0x16, 0x03, 0x01, 0x00, 0x2a, 0x02, 0x00, 0x00,
|
||||
0x26, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x16,
|
||||
0x03, 0x01, 0x02, 0xbe, 0x0b, 0x00, 0x02, 0xba,
|
||||
0x00, 0x02, 0xb7, 0x00, 0x02, 0xb4, 0x30, 0x82,
|
||||
0x02, 0xb0, 0x30, 0x82, 0x02, 0x19, 0xa0, 0x03,
|
||||
0x02, 0x01, 0x02, 0x02, 0x09, 0x00, 0x85, 0xb0,
|
||||
0xbb, 0xa4, 0x8a, 0x7f, 0xb8, 0xca, 0x30, 0x0d,
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
|
||||
0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x45, 0x31,
|
||||
0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
|
||||
0x13, 0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11,
|
||||
0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x53,
|
||||
0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74,
|
||||
0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55,
|
||||
0x04, 0x0a, 0x13, 0x18, 0x49, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64,
|
||||
0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79,
|
||||
0x20, 0x4c, 0x74, 0x64, 0x30, 0x1e, 0x17, 0x0d,
|
||||
0x31, 0x30, 0x30, 0x34, 0x32, 0x34, 0x30, 0x39,
|
||||
0x30, 0x39, 0x33, 0x38, 0x5a, 0x17, 0x0d, 0x31,
|
||||
0x31, 0x30, 0x34, 0x32, 0x34, 0x30, 0x39, 0x30,
|
||||
0x39, 0x33, 0x38, 0x5a, 0x30, 0x45, 0x31, 0x0b,
|
||||
0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
|
||||
0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06,
|
||||
0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x53, 0x6f,
|
||||
0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65,
|
||||
0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04,
|
||||
0x0a, 0x13, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67,
|
||||
0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20,
|
||||
0x4c, 0x74, 0x64, 0x30, 0x81, 0x9f, 0x30, 0x0d,
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
|
||||
0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d,
|
||||
0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00,
|
||||
0xbb, 0x79, 0xd6, 0xf5, 0x17, 0xb5, 0xe5, 0xbf,
|
||||
0x46, 0x10, 0xd0, 0xdc, 0x69, 0xbe, 0xe6, 0x2b,
|
||||
0x07, 0x43, 0x5a, 0xd0, 0x03, 0x2d, 0x8a, 0x7a,
|
||||
0x43, 0x85, 0xb7, 0x14, 0x52, 0xe7, 0xa5, 0x65,
|
||||
0x4c, 0x2c, 0x78, 0xb8, 0x23, 0x8c, 0xb5, 0xb4,
|
||||
0x82, 0xe5, 0xde, 0x1f, 0x95, 0x3b, 0x7e, 0x62,
|
||||
0xa5, 0x2c, 0xa5, 0x33, 0xd6, 0xfe, 0x12, 0x5c,
|
||||
0x7a, 0x56, 0xfc, 0xf5, 0x06, 0xbf, 0xfa, 0x58,
|
||||
0x7b, 0x26, 0x3f, 0xb5, 0xcd, 0x04, 0xd3, 0xd0,
|
||||
0xc9, 0x21, 0x96, 0x4a, 0xc7, 0xf4, 0x54, 0x9f,
|
||||
0x5a, 0xbf, 0xef, 0x42, 0x71, 0x00, 0xfe, 0x18,
|
||||
0x99, 0x07, 0x7f, 0x7e, 0x88, 0x7d, 0x7d, 0xf1,
|
||||
0x04, 0x39, 0xc4, 0xa2, 0x2e, 0xdb, 0x51, 0xc9,
|
||||
0x7c, 0xe3, 0xc0, 0x4c, 0x3b, 0x32, 0x66, 0x01,
|
||||
0xcf, 0xaf, 0xb1, 0x1d, 0xb8, 0x71, 0x9a, 0x1d,
|
||||
0xdb, 0xdb, 0x89, 0x6b, 0xae, 0xda, 0x2d, 0x79,
|
||||
0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xa7,
|
||||
0x30, 0x81, 0xa4, 0x30, 0x1d, 0x06, 0x03, 0x55,
|
||||
0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb1, 0xad,
|
||||
0xe2, 0x85, 0x5a, 0xcf, 0xcb, 0x28, 0xdb, 0x69,
|
||||
0xce, 0x23, 0x69, 0xde, 0xd3, 0x26, 0x8e, 0x18,
|
||||
0x88, 0x39, 0x30, 0x75, 0x06, 0x03, 0x55, 0x1d,
|
||||
0x23, 0x04, 0x6e, 0x30, 0x6c, 0x80, 0x14, 0xb1,
|
||||
0xad, 0xe2, 0x85, 0x5a, 0xcf, 0xcb, 0x28, 0xdb,
|
||||
0x69, 0xce, 0x23, 0x69, 0xde, 0xd3, 0x26, 0x8e,
|
||||
0x18, 0x88, 0x39, 0xa1, 0x49, 0xa4, 0x47, 0x30,
|
||||
0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
|
||||
0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x13,
|
||||
0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
|
||||
0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06,
|
||||
0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x49, 0x6e,
|
||||
0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57,
|
||||
0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50,
|
||||
0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x82, 0x09,
|
||||
0x00, 0x85, 0xb0, 0xbb, 0xa4, 0x8a, 0x7f, 0xb8,
|
||||
0xca, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13,
|
||||
0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30,
|
||||
0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
|
||||
0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81,
|
||||
0x81, 0x00, 0x08, 0x6c, 0x45, 0x24, 0xc7, 0x6b,
|
||||
0xb1, 0x59, 0xab, 0x0c, 0x52, 0xcc, 0xf2, 0xb0,
|
||||
0x14, 0xd7, 0x87, 0x9d, 0x7a, 0x64, 0x75, 0xb5,
|
||||
0x5a, 0x95, 0x66, 0xe4, 0xc5, 0x2b, 0x8e, 0xae,
|
||||
0x12, 0x66, 0x1f, 0xeb, 0x4f, 0x38, 0xb3, 0x6e,
|
||||
0x60, 0xd3, 0x92, 0xfd, 0xf7, 0x41, 0x08, 0xb5,
|
||||
0x25, 0x13, 0xb1, 0x18, 0x7a, 0x24, 0xfb, 0x30,
|
||||
0x1d, 0xba, 0xed, 0x98, 0xb9, 0x17, 0xec, 0xe7,
|
||||
0xd7, 0x31, 0x59, 0xdb, 0x95, 0xd3, 0x1d, 0x78,
|
||||
0xea, 0x50, 0x56, 0x5c, 0xd5, 0x82, 0x5a, 0x2d,
|
||||
0x5a, 0x5f, 0x33, 0xc4, 0xb6, 0xd8, 0xc9, 0x75,
|
||||
0x90, 0x96, 0x8c, 0x0f, 0x52, 0x98, 0xb5, 0xcd,
|
||||
0x98, 0x1f, 0x89, 0x20, 0x5f, 0xf2, 0xa0, 0x1c,
|
||||
0xa3, 0x1b, 0x96, 0x94, 0xdd, 0xa9, 0xfd, 0x57,
|
||||
0xe9, 0x70, 0xe8, 0x26, 0x6d, 0x71, 0x99, 0x9b,
|
||||
0x26, 0x6e, 0x38, 0x50, 0x29, 0x6c, 0x90, 0xa7,
|
||||
0xbd, 0xd9, 0x16, 0x03, 0x01, 0x00, 0x08, 0x0d,
|
||||
0x00, 0x00, 0x04, 0x01, 0x01, 0x00, 0x00, 0x16,
|
||||
0x03, 0x01, 0x00, 0x04, 0x0e, 0x00, 0x00, 0x00,
|
||||
},
|
||||
|
||||
{
|
||||
0x16, 0x03, 0x01, 0x00, 0x07, 0x0b, 0x00, 0x00,
|
||||
0x03, 0x00, 0x00, 0x00, 0x16, 0x03, 0x01, 0x00,
|
||||
0x86, 0x10, 0x00, 0x00, 0x82, 0x00, 0x80, 0x64,
|
||||
0x28, 0xb9, 0x3f, 0x48, 0xaf, 0x06, 0x22, 0x39,
|
||||
0x56, 0xd8, 0x6f, 0x63, 0x5d, 0x03, 0x48, 0x63,
|
||||
0x01, 0x13, 0xa2, 0xd6, 0x76, 0xc0, 0xab, 0xda,
|
||||
0x25, 0x30, 0x75, 0x6c, 0xaa, 0xb4, 0xdc, 0x35,
|
||||
0x72, 0xdc, 0xf2, 0x43, 0xe4, 0x1d, 0x82, 0xfb,
|
||||
0x6c, 0x64, 0xe2, 0xa7, 0x8f, 0x32, 0x67, 0x6b,
|
||||
0xcd, 0xd2, 0xb2, 0x36, 0x94, 0xbc, 0x6f, 0x46,
|
||||
0x79, 0x29, 0x42, 0xe3, 0x1a, 0xbf, 0xfb, 0x41,
|
||||
0xd5, 0xe3, 0xb4, 0x2a, 0xf6, 0x95, 0x6f, 0x0c,
|
||||
0x87, 0xb9, 0x03, 0x18, 0xa1, 0xea, 0x4a, 0xe2,
|
||||
0x2e, 0x0f, 0x50, 0x00, 0xc1, 0xe8, 0x8c, 0xc8,
|
||||
0xa2, 0xf6, 0xa4, 0x05, 0xf4, 0x38, 0x3e, 0xd9,
|
||||
0x6e, 0x63, 0x96, 0x0c, 0x34, 0x73, 0x90, 0x03,
|
||||
0x55, 0xa6, 0x34, 0xb0, 0x5e, 0x8c, 0x48, 0x40,
|
||||
0x25, 0x45, 0x84, 0xa6, 0x21, 0x3f, 0x81, 0x97,
|
||||
0xa7, 0x11, 0x09, 0x14, 0x95, 0xa5, 0xe5, 0x14,
|
||||
0x03, 0x01, 0x00, 0x01, 0x01, 0x16, 0x03, 0x01,
|
||||
0x00, 0x24, 0x16, 0xaa, 0x01, 0x2c, 0xa8, 0xc1,
|
||||
0x28, 0xaf, 0x35, 0xc1, 0xc1, 0xf3, 0x0a, 0x25,
|
||||
0x66, 0x6e, 0x27, 0x11, 0xa3, 0xa4, 0xd9, 0xe9,
|
||||
0xea, 0x15, 0x09, 0x9d, 0x28, 0xe3, 0x5b, 0x2b,
|
||||
0xa6, 0x25, 0xa7, 0x14, 0x24, 0x3a,
|
||||
},
|
||||
|
||||
{
|
||||
0x14, 0x03, 0x01, 0x00, 0x01, 0x01, 0x16, 0x03,
|
||||
0x01, 0x00, 0x24, 0x9a, 0xa8, 0xd6, 0x77, 0x46,
|
||||
0x45, 0x68, 0x9d, 0x5d, 0xa9, 0x68, 0x03, 0xe5,
|
||||
0xaf, 0xe8, 0xc8, 0x21, 0xc5, 0xc6, 0xc1, 0x50,
|
||||
0xe0, 0xd8, 0x52, 0xce, 0xa3, 0x4f, 0x2d, 0xf4,
|
||||
0xe3, 0xa7, 0x7d, 0x35, 0x80, 0x84, 0x12, 0x17,
|
||||
0x03, 0x01, 0x00, 0x21, 0x8a, 0x82, 0x0c, 0x54,
|
||||
0x1b, 0xeb, 0x77, 0x90, 0x2c, 0x3e, 0xbc, 0xf0,
|
||||
0x23, 0xcc, 0xa8, 0x9f, 0x25, 0x08, 0x12, 0xed,
|
||||
0x43, 0xf1, 0xf9, 0x06, 0xad, 0xa9, 0x4b, 0x97,
|
||||
0x82, 0xb7, 0xc4, 0x0b, 0x4c, 0x15, 0x03, 0x01,
|
||||
0x00, 0x16, 0x05, 0x2d, 0x9d, 0x45, 0x03, 0xb7,
|
||||
0xc2, 0xd1, 0xb5, 0x1a, 0x43, 0xcf, 0x1a, 0x37,
|
||||
0xf4, 0x70, 0xcc, 0xb4, 0xed, 0x07, 0x76, 0x3a,
|
||||
}}},
|
||||
// Server asks for cert with empty CA list, client gives one
|
||||
// gotest -test.run "TestRunServer" -serve -clientauth 1
|
||||
// gnutls-cli --insecure --debug 100 -p 10443 localhost
|
||||
{"RequestClientCert, client gives it", RequestClientCert,
|
||||
[]*x509.Certificate{clicert},
|
||||
[][]byte{{
|
||||
0x16, 0x03, 0x02, 0x00, 0x7a, 0x01, 0x00, 0x00,
|
||||
0x76, 0x03, 0x02, 0x4e, 0xe7, 0x44, 0xda, 0x58,
|
||||
0x7d, 0x46, 0x4a, 0x48, 0x97, 0x9f, 0xe5, 0x91,
|
||||
0x11, 0x64, 0xa7, 0x1e, 0x4d, 0xb7, 0xfe, 0x9b,
|
||||
0xc6, 0x63, 0xf8, 0xa4, 0xb5, 0x0b, 0x18, 0xb5,
|
||||
0xbd, 0x19, 0xb3, 0x00, 0x00, 0x34, 0x00, 0x33,
|
||||
0x00, 0x45, 0x00, 0x39, 0x00, 0x88, 0x00, 0x16,
|
||||
0x00, 0x32, 0x00, 0x44, 0x00, 0x38, 0x00, 0x87,
|
||||
0x00, 0x13, 0x00, 0x66, 0x00, 0x90, 0x00, 0x91,
|
||||
0x00, 0x8f, 0x00, 0x8e, 0x00, 0x2f, 0x00, 0x41,
|
||||
0x00, 0x35, 0x00, 0x84, 0x00, 0x0a, 0x00, 0x05,
|
||||
0x00, 0x04, 0x00, 0x8c, 0x00, 0x8d, 0x00, 0x8b,
|
||||
0x00, 0x8a, 0x01, 0x00, 0x00, 0x19, 0x00, 0x09,
|
||||
0x00, 0x03, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x0e, 0x00, 0x0c, 0x00, 0x00, 0x09, 0x6c, 0x6f,
|
||||
0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
|
||||
},
|
||||
|
||||
{
|
||||
0x16, 0x03, 0x01, 0x00, 0x2a, 0x02, 0x00, 0x00,
|
||||
0x26, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x16,
|
||||
0x03, 0x01, 0x02, 0xbe, 0x0b, 0x00, 0x02, 0xba,
|
||||
0x00, 0x02, 0xb7, 0x00, 0x02, 0xb4, 0x30, 0x82,
|
||||
0x02, 0xb0, 0x30, 0x82, 0x02, 0x19, 0xa0, 0x03,
|
||||
0x02, 0x01, 0x02, 0x02, 0x09, 0x00, 0x85, 0xb0,
|
||||
0xbb, 0xa4, 0x8a, 0x7f, 0xb8, 0xca, 0x30, 0x0d,
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
|
||||
0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x45, 0x31,
|
||||
0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
|
||||
0x13, 0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11,
|
||||
0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x53,
|
||||
0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74,
|
||||
0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55,
|
||||
0x04, 0x0a, 0x13, 0x18, 0x49, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64,
|
||||
0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79,
|
||||
0x20, 0x4c, 0x74, 0x64, 0x30, 0x1e, 0x17, 0x0d,
|
||||
0x31, 0x30, 0x30, 0x34, 0x32, 0x34, 0x30, 0x39,
|
||||
0x30, 0x39, 0x33, 0x38, 0x5a, 0x17, 0x0d, 0x31,
|
||||
0x31, 0x30, 0x34, 0x32, 0x34, 0x30, 0x39, 0x30,
|
||||
0x39, 0x33, 0x38, 0x5a, 0x30, 0x45, 0x31, 0x0b,
|
||||
0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
|
||||
0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06,
|
||||
0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x53, 0x6f,
|
||||
0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65,
|
||||
0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04,
|
||||
0x0a, 0x13, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67,
|
||||
0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20,
|
||||
0x4c, 0x74, 0x64, 0x30, 0x81, 0x9f, 0x30, 0x0d,
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
|
||||
0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d,
|
||||
0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00,
|
||||
0xbb, 0x79, 0xd6, 0xf5, 0x17, 0xb5, 0xe5, 0xbf,
|
||||
0x46, 0x10, 0xd0, 0xdc, 0x69, 0xbe, 0xe6, 0x2b,
|
||||
0x07, 0x43, 0x5a, 0xd0, 0x03, 0x2d, 0x8a, 0x7a,
|
||||
0x43, 0x85, 0xb7, 0x14, 0x52, 0xe7, 0xa5, 0x65,
|
||||
0x4c, 0x2c, 0x78, 0xb8, 0x23, 0x8c, 0xb5, 0xb4,
|
||||
0x82, 0xe5, 0xde, 0x1f, 0x95, 0x3b, 0x7e, 0x62,
|
||||
0xa5, 0x2c, 0xa5, 0x33, 0xd6, 0xfe, 0x12, 0x5c,
|
||||
0x7a, 0x56, 0xfc, 0xf5, 0x06, 0xbf, 0xfa, 0x58,
|
||||
0x7b, 0x26, 0x3f, 0xb5, 0xcd, 0x04, 0xd3, 0xd0,
|
||||
0xc9, 0x21, 0x96, 0x4a, 0xc7, 0xf4, 0x54, 0x9f,
|
||||
0x5a, 0xbf, 0xef, 0x42, 0x71, 0x00, 0xfe, 0x18,
|
||||
0x99, 0x07, 0x7f, 0x7e, 0x88, 0x7d, 0x7d, 0xf1,
|
||||
0x04, 0x39, 0xc4, 0xa2, 0x2e, 0xdb, 0x51, 0xc9,
|
||||
0x7c, 0xe3, 0xc0, 0x4c, 0x3b, 0x32, 0x66, 0x01,
|
||||
0xcf, 0xaf, 0xb1, 0x1d, 0xb8, 0x71, 0x9a, 0x1d,
|
||||
0xdb, 0xdb, 0x89, 0x6b, 0xae, 0xda, 0x2d, 0x79,
|
||||
0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xa7,
|
||||
0x30, 0x81, 0xa4, 0x30, 0x1d, 0x06, 0x03, 0x55,
|
||||
0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb1, 0xad,
|
||||
0xe2, 0x85, 0x5a, 0xcf, 0xcb, 0x28, 0xdb, 0x69,
|
||||
0xce, 0x23, 0x69, 0xde, 0xd3, 0x26, 0x8e, 0x18,
|
||||
0x88, 0x39, 0x30, 0x75, 0x06, 0x03, 0x55, 0x1d,
|
||||
0x23, 0x04, 0x6e, 0x30, 0x6c, 0x80, 0x14, 0xb1,
|
||||
0xad, 0xe2, 0x85, 0x5a, 0xcf, 0xcb, 0x28, 0xdb,
|
||||
0x69, 0xce, 0x23, 0x69, 0xde, 0xd3, 0x26, 0x8e,
|
||||
0x18, 0x88, 0x39, 0xa1, 0x49, 0xa4, 0x47, 0x30,
|
||||
0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
|
||||
0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x13,
|
||||
0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
|
||||
0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06,
|
||||
0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x49, 0x6e,
|
||||
0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57,
|
||||
0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50,
|
||||
0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x82, 0x09,
|
||||
0x00, 0x85, 0xb0, 0xbb, 0xa4, 0x8a, 0x7f, 0xb8,
|
||||
0xca, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13,
|
||||
0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30,
|
||||
0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
|
||||
0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81,
|
||||
0x81, 0x00, 0x08, 0x6c, 0x45, 0x24, 0xc7, 0x6b,
|
||||
0xb1, 0x59, 0xab, 0x0c, 0x52, 0xcc, 0xf2, 0xb0,
|
||||
0x14, 0xd7, 0x87, 0x9d, 0x7a, 0x64, 0x75, 0xb5,
|
||||
0x5a, 0x95, 0x66, 0xe4, 0xc5, 0x2b, 0x8e, 0xae,
|
||||
0x12, 0x66, 0x1f, 0xeb, 0x4f, 0x38, 0xb3, 0x6e,
|
||||
0x60, 0xd3, 0x92, 0xfd, 0xf7, 0x41, 0x08, 0xb5,
|
||||
0x25, 0x13, 0xb1, 0x18, 0x7a, 0x24, 0xfb, 0x30,
|
||||
0x1d, 0xba, 0xed, 0x98, 0xb9, 0x17, 0xec, 0xe7,
|
||||
0xd7, 0x31, 0x59, 0xdb, 0x95, 0xd3, 0x1d, 0x78,
|
||||
0xea, 0x50, 0x56, 0x5c, 0xd5, 0x82, 0x5a, 0x2d,
|
||||
0x5a, 0x5f, 0x33, 0xc4, 0xb6, 0xd8, 0xc9, 0x75,
|
||||
0x90, 0x96, 0x8c, 0x0f, 0x52, 0x98, 0xb5, 0xcd,
|
||||
0x98, 0x1f, 0x89, 0x20, 0x5f, 0xf2, 0xa0, 0x1c,
|
||||
0xa3, 0x1b, 0x96, 0x94, 0xdd, 0xa9, 0xfd, 0x57,
|
||||
0xe9, 0x70, 0xe8, 0x26, 0x6d, 0x71, 0x99, 0x9b,
|
||||
0x26, 0x6e, 0x38, 0x50, 0x29, 0x6c, 0x90, 0xa7,
|
||||
0xbd, 0xd9, 0x16, 0x03, 0x01, 0x00, 0x08, 0x0d,
|
||||
0x00, 0x00, 0x04, 0x01, 0x01, 0x00, 0x00, 0x16,
|
||||
0x03, 0x01, 0x00, 0x04, 0x0e, 0x00, 0x00, 0x00,
|
||||
},
|
||||
|
||||
{
|
||||
0x16, 0x03, 0x01, 0x01, 0xfb, 0x0b, 0x00, 0x01,
|
||||
0xf7, 0x00, 0x01, 0xf4, 0x00, 0x01, 0xf1, 0x30,
|
||||
0x82, 0x01, 0xed, 0x30, 0x82, 0x01, 0x58, 0xa0,
|
||||
0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x00, 0x30,
|
||||
0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
|
||||
0x0d, 0x01, 0x01, 0x05, 0x30, 0x26, 0x31, 0x10,
|
||||
0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
|
||||
0x07, 0x41, 0x63, 0x6d, 0x65, 0x20, 0x43, 0x6f,
|
||||
0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04,
|
||||
0x03, 0x13, 0x09, 0x31, 0x32, 0x37, 0x2e, 0x30,
|
||||
0x2e, 0x30, 0x2e, 0x31, 0x30, 0x1e, 0x17, 0x0d,
|
||||
0x31, 0x31, 0x31, 0x32, 0x30, 0x38, 0x30, 0x37,
|
||||
0x35, 0x35, 0x31, 0x32, 0x5a, 0x17, 0x0d, 0x31,
|
||||
0x32, 0x31, 0x32, 0x30, 0x37, 0x30, 0x38, 0x30,
|
||||
0x30, 0x31, 0x32, 0x5a, 0x30, 0x26, 0x31, 0x10,
|
||||
0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
|
||||
0x07, 0x41, 0x63, 0x6d, 0x65, 0x20, 0x43, 0x6f,
|
||||
0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04,
|
||||
0x03, 0x13, 0x09, 0x31, 0x32, 0x37, 0x2e, 0x30,
|
||||
0x2e, 0x30, 0x2e, 0x31, 0x30, 0x81, 0x9c, 0x30,
|
||||
0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
|
||||
0x0d, 0x01, 0x01, 0x01, 0x03, 0x81, 0x8c, 0x00,
|
||||
0x30, 0x81, 0x88, 0x02, 0x81, 0x80, 0x4e, 0xd0,
|
||||
0x7b, 0x31, 0xe3, 0x82, 0x64, 0xd9, 0x59, 0xc0,
|
||||
0xc2, 0x87, 0xa4, 0x5e, 0x1e, 0x8b, 0x73, 0x33,
|
||||
0xc7, 0x63, 0x53, 0xdf, 0x66, 0x92, 0x06, 0x84,
|
||||
0xf6, 0x64, 0xd5, 0x8f, 0xe4, 0x36, 0xa7, 0x1d,
|
||||
0x2b, 0xe8, 0xb3, 0x20, 0x36, 0x45, 0x23, 0xb5,
|
||||
0xe3, 0x95, 0xae, 0xed, 0xe0, 0xf5, 0x20, 0x9c,
|
||||
0x8d, 0x95, 0xdf, 0x7f, 0x5a, 0x12, 0xef, 0x87,
|
||||
0xe4, 0x5b, 0x68, 0xe4, 0xe9, 0x0e, 0x74, 0xec,
|
||||
0x04, 0x8a, 0x7f, 0xde, 0x93, 0x27, 0xc4, 0x01,
|
||||
0x19, 0x7a, 0xbd, 0xf2, 0xdc, 0x3d, 0x14, 0xab,
|
||||
0xd0, 0x54, 0xca, 0x21, 0x0c, 0xd0, 0x4d, 0x6e,
|
||||
0x87, 0x2e, 0x5c, 0xc5, 0xd2, 0xbb, 0x4d, 0x4b,
|
||||
0x4f, 0xce, 0xb6, 0x2c, 0xf7, 0x7e, 0x88, 0xec,
|
||||
0x7c, 0xd7, 0x02, 0x91, 0x74, 0xa6, 0x1e, 0x0c,
|
||||
0x1a, 0xda, 0xe3, 0x4a, 0x5a, 0x2e, 0xde, 0x13,
|
||||
0x9c, 0x4c, 0x40, 0x88, 0x59, 0x93, 0x02, 0x03,
|
||||
0x01, 0x00, 0x01, 0xa3, 0x32, 0x30, 0x30, 0x30,
|
||||
0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
|
||||
0xff, 0x04, 0x04, 0x03, 0x02, 0x00, 0xa0, 0x30,
|
||||
0x0d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x06,
|
||||
0x04, 0x04, 0x01, 0x02, 0x03, 0x04, 0x30, 0x0f,
|
||||
0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x08, 0x30,
|
||||
0x06, 0x80, 0x04, 0x01, 0x02, 0x03, 0x04, 0x30,
|
||||
0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
|
||||
0x0d, 0x01, 0x01, 0x05, 0x03, 0x81, 0x81, 0x00,
|
||||
0x36, 0x1f, 0xb3, 0x7a, 0x0c, 0x75, 0xc9, 0x6e,
|
||||
0x37, 0x46, 0x61, 0x2b, 0xd5, 0xbd, 0xc0, 0xa7,
|
||||
0x4b, 0xcc, 0x46, 0x9a, 0x81, 0x58, 0x7c, 0x85,
|
||||
0x79, 0x29, 0xc8, 0xc8, 0xc6, 0x67, 0xdd, 0x32,
|
||||
0x56, 0x45, 0x2b, 0x75, 0xb6, 0xe9, 0x24, 0xa9,
|
||||
0x50, 0x9a, 0xbe, 0x1f, 0x5a, 0xfa, 0x1a, 0x15,
|
||||
0xd9, 0xcc, 0x55, 0x95, 0x72, 0x16, 0x83, 0xb9,
|
||||
0xc2, 0xb6, 0x8f, 0xfd, 0x88, 0x8c, 0x38, 0x84,
|
||||
0x1d, 0xab, 0x5d, 0x92, 0x31, 0x13, 0x4f, 0xfd,
|
||||
0x83, 0x3b, 0xc6, 0x9d, 0xf1, 0x11, 0x62, 0xb6,
|
||||
0x8b, 0xec, 0xab, 0x67, 0xbe, 0xc8, 0x64, 0xb0,
|
||||
0x11, 0x50, 0x46, 0x58, 0x17, 0x6b, 0x99, 0x1c,
|
||||
0xd3, 0x1d, 0xfc, 0x06, 0xf1, 0x0e, 0xe5, 0x96,
|
||||
0xa8, 0x0c, 0xf9, 0x78, 0x20, 0xb7, 0x44, 0x18,
|
||||
0x51, 0x8d, 0x10, 0x7e, 0x4f, 0x94, 0x67, 0xdf,
|
||||
0xa3, 0x4e, 0x70, 0x73, 0x8e, 0x90, 0x91, 0x85,
|
||||
0x16, 0x03, 0x01, 0x00, 0x86, 0x10, 0x00, 0x00,
|
||||
0x82, 0x00, 0x80, 0xa7, 0x2f, 0xed, 0xfa, 0xc2,
|
||||
0xbd, 0x46, 0xa1, 0xf2, 0x69, 0xc5, 0x1d, 0xa1,
|
||||
0x34, 0xd6, 0xd0, 0x84, 0xf5, 0x5d, 0x8c, 0x82,
|
||||
0x8d, 0x98, 0x82, 0x9c, 0xd9, 0x07, 0xe0, 0xf7,
|
||||
0x55, 0x49, 0x4d, 0xa1, 0x48, 0x59, 0x02, 0xd3,
|
||||
0x84, 0x37, 0xaf, 0x01, 0xb3, 0x3a, 0xf4, 0xed,
|
||||
0x99, 0xbe, 0x67, 0x36, 0x19, 0x55, 0xf3, 0xf9,
|
||||
0xcb, 0x94, 0xe5, 0x7b, 0x8b, 0x77, 0xf2, 0x5f,
|
||||
0x4c, 0xfe, 0x01, 0x1f, 0x7b, 0xd7, 0x23, 0x49,
|
||||
0x0c, 0xcb, 0x6c, 0xb0, 0xe7, 0x77, 0xd6, 0xcf,
|
||||
0xa8, 0x7d, 0xdb, 0xa7, 0x14, 0xe2, 0xf5, 0xf3,
|
||||
0xff, 0xba, 0x23, 0xd2, 0x9a, 0x36, 0x14, 0x60,
|
||||
0x2a, 0x91, 0x5d, 0x2b, 0x35, 0x3b, 0xb6, 0xdd,
|
||||
0xcb, 0x6b, 0xdc, 0x18, 0xdc, 0x33, 0xb8, 0xb3,
|
||||
0xc7, 0x27, 0x7e, 0xfc, 0xd2, 0xf7, 0x97, 0x90,
|
||||
0x5e, 0x17, 0xac, 0x14, 0x8e, 0x0f, 0xca, 0xb5,
|
||||
0x6f, 0xc9, 0x2d, 0x16, 0x03, 0x01, 0x00, 0x86,
|
||||
0x0f, 0x00, 0x00, 0x82, 0x00, 0x80, 0x44, 0x7f,
|
||||
0xa2, 0x59, 0x60, 0x0b, 0x5a, 0xc4, 0xaf, 0x1e,
|
||||
0x60, 0xa5, 0x24, 0xea, 0xc1, 0xc3, 0x22, 0x21,
|
||||
0x6b, 0x22, 0x8b, 0x2a, 0x11, 0x82, 0x68, 0x7d,
|
||||
0xb9, 0xdd, 0x9c, 0x27, 0x4c, 0xc2, 0xc8, 0xa2,
|
||||
0x8b, 0x6b, 0x77, 0x8d, 0x3a, 0x2b, 0x8d, 0x2f,
|
||||
0x6a, 0x2b, 0x43, 0xd2, 0xd1, 0xc6, 0x41, 0x79,
|
||||
0xa2, 0x4f, 0x2b, 0xc2, 0xf7, 0xb2, 0x10, 0xad,
|
||||
0xa6, 0x01, 0x51, 0x51, 0x25, 0xe7, 0x58, 0x7a,
|
||||
0xcf, 0x3b, 0xc4, 0x29, 0xb5, 0xe5, 0xa7, 0x83,
|
||||
0xe6, 0xcb, 0x1e, 0xf3, 0x02, 0x0f, 0x53, 0x3b,
|
||||
0xb5, 0x39, 0xef, 0x9c, 0x42, 0xe0, 0xa6, 0x9b,
|
||||
0x2b, 0xdd, 0x60, 0xae, 0x0a, 0x73, 0x35, 0xbe,
|
||||
0x26, 0x10, 0x1b, 0xe9, 0xe9, 0x61, 0xab, 0x20,
|
||||
0xa5, 0x48, 0xc6, 0x60, 0xa6, 0x50, 0x3c, 0xfb,
|
||||
0xa7, 0xca, 0xb0, 0x80, 0x95, 0x1e, 0xce, 0xc7,
|
||||
0xbb, 0x68, 0x44, 0xdc, 0x0e, 0x0e, 0x14, 0x03,
|
||||
0x01, 0x00, 0x01, 0x01, 0x16, 0x03, 0x01, 0x00,
|
||||
0x24, 0xb6, 0xcd, 0x0c, 0x78, 0xfd, 0xd6, 0xff,
|
||||
0xbe, 0x97, 0xd5, 0x0a, 0x7d, 0x4f, 0xa1, 0x03,
|
||||
0x78, 0xc8, 0x61, 0x6f, 0xf2, 0x4b, 0xa8, 0x56,
|
||||
0x4f, 0x3c, 0xa2, 0xd9, 0xd0, 0x20, 0x13, 0x1b,
|
||||
0x8b, 0x36, 0xb7, 0x33, 0x9c,
|
||||
},
|
||||
|
||||
{
|
||||
0x14, 0x03, 0x01, 0x00, 0x01, 0x01, 0x16, 0x03,
|
||||
0x01, 0x00, 0x24, 0xa3, 0x43, 0x94, 0xe7, 0xdf,
|
||||
0xb6, 0xc3, 0x03, 0x9f, 0xc1, 0x59, 0x0c, 0xc3,
|
||||
0x13, 0xae, 0xed, 0xcf, 0xff, 0xf1, 0x80, 0xf3,
|
||||
0x13, 0x63, 0x1c, 0xf0, 0xca, 0xad, 0x9e, 0x71,
|
||||
0x46, 0x5f, 0x6b, 0xeb, 0x10, 0x3f, 0xe3, 0x17,
|
||||
0x03, 0x01, 0x00, 0x21, 0xe9, 0x80, 0x95, 0x6e,
|
||||
0x05, 0x55, 0x2f, 0xed, 0x4d, 0xde, 0x17, 0x3a,
|
||||
0x32, 0x9b, 0x2a, 0x74, 0x30, 0x4f, 0xe0, 0x9f,
|
||||
0x4e, 0xd3, 0x06, 0xbd, 0x3a, 0x43, 0x75, 0x8b,
|
||||
0x5b, 0x9a, 0xd8, 0x2e, 0x56, 0x15, 0x03, 0x01,
|
||||
0x00, 0x16, 0x53, 0xf5, 0xff, 0xe0, 0xa1, 0x6c,
|
||||
0x33, 0xf4, 0x4e, 0x89, 0x68, 0xe1, 0xf7, 0x61,
|
||||
0x13, 0xb3, 0x12, 0xa1, 0x8e, 0x5a, 0x7a, 0x02,
|
||||
}}},
|
||||
}
|
||||
|
||||
// cert.pem and key.pem were generated with generate_cert.go
|
||||
// Thus, they have no ExtKeyUsage fields and trigger an error
|
||||
// when verification is turned on.
|
||||
|
||||
var clicert = loadPEMCert(`
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB7TCCAVigAwIBAgIBADALBgkqhkiG9w0BAQUwJjEQMA4GA1UEChMHQWNtZSBD
|
||||
bzESMBAGA1UEAxMJMTI3LjAuMC4xMB4XDTExMTIwODA3NTUxMloXDTEyMTIwNzA4
|
||||
MDAxMlowJjEQMA4GA1UEChMHQWNtZSBDbzESMBAGA1UEAxMJMTI3LjAuMC4xMIGc
|
||||
MAsGCSqGSIb3DQEBAQOBjAAwgYgCgYBO0Hsx44Jk2VnAwoekXh6LczPHY1PfZpIG
|
||||
hPZk1Y/kNqcdK+izIDZFI7Xjla7t4PUgnI2V339aEu+H5Fto5OkOdOwEin/ekyfE
|
||||
ARl6vfLcPRSr0FTKIQzQTW6HLlzF0rtNS0/Otiz3fojsfNcCkXSmHgwa2uNKWi7e
|
||||
E5xMQIhZkwIDAQABozIwMDAOBgNVHQ8BAf8EBAMCAKAwDQYDVR0OBAYEBAECAwQw
|
||||
DwYDVR0jBAgwBoAEAQIDBDALBgkqhkiG9w0BAQUDgYEANh+zegx1yW43RmEr1b3A
|
||||
p0vMRpqBWHyFeSnIyMZn3TJWRSt1tukkqVCavh9a+hoV2cxVlXIWg7nCto/9iIw4
|
||||
hB2rXZIxE0/9gzvGnfERYraL7KtnvshksBFQRlgXa5kc0x38BvEO5ZaoDPl4ILdE
|
||||
GFGNEH5PlGffo05wc46QkYU=
|
||||
-----END CERTIFICATE-----
|
||||
`)
|
||||
|
||||
/* corresponding key.pem for cert.pem is:
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgE7QezHjgmTZWcDCh6ReHotzM8djU99mkgaE9mTVj+Q2px0r6LMg
|
||||
NkUjteOVru3g9SCcjZXff1oS74fkW2jk6Q507ASKf96TJ8QBGXq98tw9FKvQVMoh
|
||||
DNBNbocuXMXSu01LT862LPd+iOx81wKRdKYeDBra40paLt4TnExAiFmTAgMBAAEC
|
||||
gYBxvXd8yNteFTns8A/2yomEMC4yeosJJSpp1CsN3BJ7g8/qTnrVPxBy+RU+qr63
|
||||
t2WquaOu/cr5P8iEsa6lk20tf8pjKLNXeX0b1RTzK8rJLbS7nGzP3tvOhL096VtQ
|
||||
dAo4ROEaro0TzYpHmpciSvxVIeEIAAdFDObDJPKqcJAxyQJBAJizfYgK8Gzx9fsx
|
||||
hxp+VteCbVPg2euASH5Yv3K5LukRdKoSzHE2grUVQgN/LafC0eZibRanxHegYSr7
|
||||
7qaswKUCQQCEIWor/X4XTMdVj3Oj+vpiw75y/S9gh682+myZL+d/02IEkwnB098P
|
||||
RkKVpenBHyrGg0oeN5La7URILWKj7CPXAkBKo6F+d+phNjwIFoN1Xb/RA32w/D1I
|
||||
saG9sF+UEhRt9AxUfW/U/tIQ9V0ZHHcSg1XaCM5Nvp934brdKdvTOKnJAkBD5h/3
|
||||
Rybatlvg/fzBEaJFyq09zhngkxlZOUtBVTqzl17RVvY2orgH02U4HbCHy4phxOn7
|
||||
qTdQRYlHRftgnWK1AkANibn9PRYJ7mJyJ9Dyj2QeNcSkSTzrt0tPvUMf4+meJymN
|
||||
1Ntu5+S1DLLzfxlaljWG6ylW6DNxujCyuXIV2rvAMAA=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
*/
|
||||
|
|
|
@ -120,7 +120,7 @@ func Dial(network, addr string, config *Config) (*Conn, error) {
|
|||
|
||||
// LoadX509KeyPair reads and parses a public/private key pair from a pair of
|
||||
// files. The files must contain PEM encoded data.
|
||||
func LoadX509KeyPair(certFile string, keyFile string) (cert Certificate, err error) {
|
||||
func LoadX509KeyPair(certFile, keyFile string) (cert Certificate, err error) {
|
||||
certPEMBlock, err := ioutil.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
|
@ -101,3 +101,13 @@ func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// Subjects returns a list of the DER-encoded subjects of
|
||||
// all of the certificates in the pool.
|
||||
func (s *CertPool) Subjects() (res [][]byte) {
|
||||
res = make([][]byte, len(s.certs))
|
||||
for i, c := range s.certs {
|
||||
res[i] = c.RawSubject
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,14 +7,14 @@ package gosym
|
|||
import (
|
||||
"debug/elf"
|
||||
"os"
|
||||
"syscall"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func dotest() bool {
|
||||
// For now, only works on ELF platforms.
|
||||
// TODO: convert to work with new go tool
|
||||
return false && syscall.OS == "linux" && os.Getenv("GOARCH") == "amd64"
|
||||
return false && runtime.GOOS == "linux" && runtime.GOARCH == "amd64"
|
||||
}
|
||||
|
||||
func getTable(t *testing.T) *Table {
|
||||
|
|
|
@ -786,7 +786,8 @@ func setDefaultValue(v reflect.Value, params fieldParameters) (ok bool) {
|
|||
// Because Unmarshal uses the reflect package, the structs
|
||||
// being written to must use upper case field names.
|
||||
//
|
||||
// An ASN.1 INTEGER can be written to an int, int32 or int64.
|
||||
// An ASN.1 INTEGER can be written to an int, int32, int64,
|
||||
// or *big.Int (from the math/big package).
|
||||
// If the encoded value does not fit in the Go type,
|
||||
// Unmarshal returns a parse error.
|
||||
//
|
||||
|
|
|
@ -6,6 +6,7 @@ package asn1
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -351,6 +352,10 @@ type TestElementsAfterString struct {
|
|||
A, B int
|
||||
}
|
||||
|
||||
type TestBigInt struct {
|
||||
X *big.Int
|
||||
}
|
||||
|
||||
var unmarshalTestData = []struct {
|
||||
in []byte
|
||||
out interface{}
|
||||
|
@ -369,6 +374,7 @@ var unmarshalTestData = []struct {
|
|||
{[]byte{0x01, 0x01, 0x00}, newBool(false)},
|
||||
{[]byte{0x01, 0x01, 0x01}, newBool(true)},
|
||||
{[]byte{0x30, 0x0b, 0x13, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x01, 0x22, 0x02, 0x01, 0x33}, &TestElementsAfterString{"foo", 0x22, 0x33}},
|
||||
{[]byte{0x30, 0x05, 0x02, 0x03, 0x12, 0x34, 0x56}, &TestBigInt{big.NewInt(0x123456)}},
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
|
|
|
@ -7,6 +7,7 @@ package asn1
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
@ -20,6 +21,10 @@ type twoIntStruct struct {
|
|||
B int
|
||||
}
|
||||
|
||||
type bigIntStruct struct {
|
||||
A *big.Int
|
||||
}
|
||||
|
||||
type nestedStruct struct {
|
||||
A intStruct
|
||||
}
|
||||
|
@ -65,6 +70,7 @@ var marshalTests = []marshalTest{
|
|||
{-128, "020180"},
|
||||
{-129, "0202ff7f"},
|
||||
{intStruct{64}, "3003020140"},
|
||||
{bigIntStruct{big.NewInt(0x123456)}, "30050203123456"},
|
||||
{twoIntStruct{64, 65}, "3006020140020141"},
|
||||
{nestedStruct{intStruct{127}}, "3005300302017f"},
|
||||
{[]byte{1, 2, 3}, "0403010203"},
|
||||
|
|
|
@ -1039,9 +1039,9 @@ func (dec *Decoder) compatibleType(fr reflect.Type, fw typeId, inProgress map[re
|
|||
// Extract and compare element types.
|
||||
var sw *sliceType
|
||||
if tt, ok := builtinIdToType[fw]; ok {
|
||||
sw = tt.(*sliceType)
|
||||
} else {
|
||||
sw = dec.wireType[fw].SliceT
|
||||
sw, _ = tt.(*sliceType)
|
||||
} else if wire != nil {
|
||||
sw = wire.SliceT
|
||||
}
|
||||
elem := userType(t.Elem()).base
|
||||
return sw != nil && dec.compatibleType(elem, sw.Elem, inProgress)
|
||||
|
|
|
@ -678,3 +678,11 @@ func TestUnexportedChan(t *testing.T) {
|
|||
t.Fatalf("error encoding unexported channel: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceIncompatibility(t *testing.T) {
|
||||
var in = []byte{1, 2, 3}
|
||||
var out []int
|
||||
if err := encAndDec(in, &out); err == nil {
|
||||
t.Error("expected compatibility error")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ package json
|
|||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
@ -538,7 +539,7 @@ func (d *decodeState) object(v reflect.Value) {
|
|||
// Read value.
|
||||
if destring {
|
||||
d.value(reflect.ValueOf(&d.tempstr))
|
||||
d.literalStore([]byte(d.tempstr), subv)
|
||||
d.literalStore([]byte(d.tempstr), subv, true)
|
||||
} else {
|
||||
d.value(subv)
|
||||
}
|
||||
|
@ -571,11 +572,15 @@ func (d *decodeState) literal(v reflect.Value) {
|
|||
d.off--
|
||||
d.scan.undo(op)
|
||||
|
||||
d.literalStore(d.data[start:d.off], v)
|
||||
d.literalStore(d.data[start:d.off], v, false)
|
||||
}
|
||||
|
||||
// literalStore decodes a literal stored in item into v.
|
||||
func (d *decodeState) literalStore(item []byte, v reflect.Value) {
|
||||
//
|
||||
// fromQuoted indicates whether this literal came from unwrapping a
|
||||
// string from the ",string" struct tag option. this is used only to
|
||||
// produce more helpful error messages.
|
||||
func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) {
|
||||
// Check for unmarshaler.
|
||||
wantptr := item[0] == 'n' // null
|
||||
unmarshaler, pv := d.indirect(v, wantptr)
|
||||
|
@ -601,7 +606,11 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value) {
|
|||
value := c == 't'
|
||||
switch v.Kind() {
|
||||
default:
|
||||
d.saveError(&UnmarshalTypeError{"bool", v.Type()})
|
||||
if fromQuoted {
|
||||
d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
|
||||
} else {
|
||||
d.saveError(&UnmarshalTypeError{"bool", v.Type()})
|
||||
}
|
||||
case reflect.Bool:
|
||||
v.SetBool(value)
|
||||
case reflect.Interface:
|
||||
|
@ -611,7 +620,11 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value) {
|
|||
case '"': // string
|
||||
s, ok := unquoteBytes(item)
|
||||
if !ok {
|
||||
d.error(errPhase)
|
||||
if fromQuoted {
|
||||
d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
|
||||
} else {
|
||||
d.error(errPhase)
|
||||
}
|
||||
}
|
||||
switch v.Kind() {
|
||||
default:
|
||||
|
@ -636,12 +649,20 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value) {
|
|||
|
||||
default: // number
|
||||
if c != '-' && (c < '0' || c > '9') {
|
||||
d.error(errPhase)
|
||||
if fromQuoted {
|
||||
d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
|
||||
} else {
|
||||
d.error(errPhase)
|
||||
}
|
||||
}
|
||||
s := string(item)
|
||||
switch v.Kind() {
|
||||
default:
|
||||
d.error(&UnmarshalTypeError{"number", v.Type()})
|
||||
if fromQuoted {
|
||||
d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
|
||||
} else {
|
||||
d.error(&UnmarshalTypeError{"number", v.Type()})
|
||||
}
|
||||
case reflect.Interface:
|
||||
n, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
|
|
|
@ -258,13 +258,10 @@ type wrongStringTest struct {
|
|||
in, err string
|
||||
}
|
||||
|
||||
// TODO(bradfitz): as part of Issue 2331, fix these tests' expected
|
||||
// error values to be helpful, rather than the confusing messages they
|
||||
// are now.
|
||||
var wrongStringTests = []wrongStringTest{
|
||||
{`{"result":"x"}`, "JSON decoder out of sync - data changing underfoot?"},
|
||||
{`{"result":"foo"}`, "json: cannot unmarshal bool into Go value of type string"},
|
||||
{`{"result":"123"}`, "json: cannot unmarshal number into Go value of type string"},
|
||||
{`{"result":"x"}`, `json: invalid use of ,string struct tag, trying to unmarshal "x" into string`},
|
||||
{`{"result":"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "foo" into string`},
|
||||
{`{"result":"123"}`, `json: invalid use of ,string struct tag, trying to unmarshal "123" into string`},
|
||||
}
|
||||
|
||||
// If people misuse the ,string modifier, the error message should be
|
||||
|
|
|
@ -12,6 +12,7 @@ package json
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"math"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
|
@ -170,6 +171,15 @@ func (e *UnsupportedTypeError) Error() string {
|
|||
return "json: unsupported type: " + e.Type.String()
|
||||
}
|
||||
|
||||
type UnsupportedValueError struct {
|
||||
Value reflect.Value
|
||||
Str string
|
||||
}
|
||||
|
||||
func (e *UnsupportedValueError) Error() string {
|
||||
return "json: unsupported value: " + e.Str
|
||||
}
|
||||
|
||||
type InvalidUTF8Error struct {
|
||||
S string
|
||||
}
|
||||
|
@ -290,7 +300,11 @@ func (e *encodeState) reflectValueQuoted(v reflect.Value, quoted bool) {
|
|||
e.Write(b)
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
b := strconv.AppendFloat(e.scratch[:0], v.Float(), 'g', -1, v.Type().Bits())
|
||||
f := v.Float()
|
||||
if math.IsInf(f, 0) || math.IsNaN(f) {
|
||||
e.error(&UnsupportedValueError{v, strconv.FormatFloat(f, 'g', -1, v.Type().Bits())})
|
||||
}
|
||||
b := strconv.AppendFloat(e.scratch[:0], f, 'g', -1, v.Type().Bits())
|
||||
if quoted {
|
||||
writeString(e, string(b))
|
||||
} else {
|
||||
|
|
|
@ -6,6 +6,7 @@ package json
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
@ -107,3 +108,21 @@ func TestEncodeRenamedByteSlice(t *testing.T) {
|
|||
t.Errorf(" got %s want %s", result, expect)
|
||||
}
|
||||
}
|
||||
|
||||
var unsupportedValues = []interface{}{
|
||||
math.NaN(),
|
||||
math.Inf(-1),
|
||||
math.Inf(1),
|
||||
}
|
||||
|
||||
func TestUnsupportedValues(t *testing.T) {
|
||||
for _, v := range unsupportedValues {
|
||||
if _, err := Marshal(v); err != nil {
|
||||
if _, ok := err.(*UnsupportedValueError); !ok {
|
||||
t.Errorf("for %v, got %T want UnsupportedValueError", v, err)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("for %v, expected error", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package xml
|
||||
|
||||
var atomValue = &Feed{
|
||||
XMLName: Name{"http://www.w3.org/2005/Atom", "feed"},
|
||||
Title: "Example Feed",
|
||||
Link: []Link{{Href: "http://example.org/"}},
|
||||
Updated: ParseTime("2003-12-13T18:30:02Z"),
|
||||
|
@ -24,19 +25,19 @@ var atomValue = &Feed{
|
|||
|
||||
var atomXml = `` +
|
||||
`<feed xmlns="http://www.w3.org/2005/Atom">` +
|
||||
`<Title>Example Feed</Title>` +
|
||||
`<Id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</Id>` +
|
||||
`<Link href="http://example.org/"></Link>` +
|
||||
`<Updated>2003-12-13T18:30:02Z</Updated>` +
|
||||
`<Author><Name>John Doe</Name><URI></URI><Email></Email></Author>` +
|
||||
`<Entry>` +
|
||||
`<Title>Atom-Powered Robots Run Amok</Title>` +
|
||||
`<Id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</Id>` +
|
||||
`<Link href="http://example.org/2003/12/13/atom03"></Link>` +
|
||||
`<Updated>2003-12-13T18:30:02Z</Updated>` +
|
||||
`<Author><Name></Name><URI></URI><Email></Email></Author>` +
|
||||
`<Summary>Some text.</Summary>` +
|
||||
`</Entry>` +
|
||||
`<title>Example Feed</title>` +
|
||||
`<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>` +
|
||||
`<link href="http://example.org/"></link>` +
|
||||
`<updated>2003-12-13T18:30:02Z</updated>` +
|
||||
`<author><name>John Doe</name><uri></uri><email></email></author>` +
|
||||
`<entry>` +
|
||||
`<title>Atom-Powered Robots Run Amok</title>` +
|
||||
`<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>` +
|
||||
`<link href="http://example.org/2003/12/13/atom03"></link>` +
|
||||
`<updated>2003-12-13T18:30:02Z</updated>` +
|
||||
`<author><name></name><uri></uri><email></email></author>` +
|
||||
`<summary>Some text.</summary>` +
|
||||
`</entry>` +
|
||||
`</feed>`
|
||||
|
||||
func ParseTime(str string) Time {
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
// Copyright 2010 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 xml
|
||||
|
||||
import "testing"
|
||||
|
||||
type C struct {
|
||||
Name string
|
||||
Open bool
|
||||
}
|
||||
|
||||
type A struct {
|
||||
XMLName Name `xml:"http://domain a"`
|
||||
C
|
||||
B B
|
||||
FieldA string
|
||||
}
|
||||
|
||||
type B struct {
|
||||
XMLName Name `xml:"b"`
|
||||
C
|
||||
FieldB string
|
||||
}
|
||||
|
||||
const _1a = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<a xmlns="http://domain">
|
||||
<name>KmlFile</name>
|
||||
<open>1</open>
|
||||
<b>
|
||||
<name>Absolute</name>
|
||||
<open>0</open>
|
||||
<fieldb>bar</fieldb>
|
||||
</b>
|
||||
<fielda>foo</fielda>
|
||||
</a>
|
||||
`
|
||||
|
||||
// Tests that embedded structs are marshalled.
|
||||
func TestEmbedded1(t *testing.T) {
|
||||
var a A
|
||||
if e := Unmarshal(StringReader(_1a), &a); e != nil {
|
||||
t.Fatalf("Unmarshal: %s", e)
|
||||
}
|
||||
if a.FieldA != "foo" {
|
||||
t.Fatalf("Unmarshal: expected 'foo' but found '%s'", a.FieldA)
|
||||
}
|
||||
if a.Name != "KmlFile" {
|
||||
t.Fatalf("Unmarshal: expected 'KmlFile' but found '%s'", a.Name)
|
||||
}
|
||||
if !a.Open {
|
||||
t.Fatal("Unmarshal: expected 'true' but found otherwise")
|
||||
}
|
||||
if a.B.FieldB != "bar" {
|
||||
t.Fatalf("Unmarshal: expected 'bar' but found '%s'", a.B.FieldB)
|
||||
}
|
||||
if a.B.Name != "Absolute" {
|
||||
t.Fatalf("Unmarshal: expected 'Absolute' but found '%s'", a.B.Name)
|
||||
}
|
||||
if a.B.Open {
|
||||
t.Fatal("Unmarshal: expected 'false' but found otherwise")
|
||||
}
|
||||
}
|
||||
|
||||
type A2 struct {
|
||||
XMLName Name `xml:"http://domain a"`
|
||||
XY string
|
||||
Xy string
|
||||
}
|
||||
|
||||
const _2a = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<a xmlns="http://domain">
|
||||
<xy>foo</xy>
|
||||
</a>
|
||||
`
|
||||
|
||||
// Tests that conflicting field names get excluded.
|
||||
func TestEmbedded2(t *testing.T) {
|
||||
var a A2
|
||||
if e := Unmarshal(StringReader(_2a), &a); e != nil {
|
||||
t.Fatalf("Unmarshal: %s", e)
|
||||
}
|
||||
if a.XY != "" {
|
||||
t.Fatalf("Unmarshal: expected empty string but found '%s'", a.XY)
|
||||
}
|
||||
if a.Xy != "" {
|
||||
t.Fatalf("Unmarshal: expected empty string but found '%s'", a.Xy)
|
||||
}
|
||||
}
|
||||
|
||||
type A3 struct {
|
||||
XMLName Name `xml:"http://domain a"`
|
||||
xy string
|
||||
}
|
||||
|
||||
// Tests that private fields are not set.
|
||||
func TestEmbedded3(t *testing.T) {
|
||||
var a A3
|
||||
if e := Unmarshal(StringReader(_2a), &a); e != nil {
|
||||
t.Fatalf("Unmarshal: %s", e)
|
||||
}
|
||||
if a.xy != "" {
|
||||
t.Fatalf("Unmarshal: expected empty string but found '%s'", a.xy)
|
||||
}
|
||||
}
|
||||
|
||||
type A4 struct {
|
||||
XMLName Name `xml:"http://domain a"`
|
||||
Any string
|
||||
}
|
||||
|
||||
// Tests that private fields are not set.
|
||||
func TestEmbedded4(t *testing.T) {
|
||||
var a A4
|
||||
if e := Unmarshal(StringReader(_2a), &a); e != nil {
|
||||
t.Fatalf("Unmarshal: %s", e)
|
||||
}
|
||||
if a.Any != "foo" {
|
||||
t.Fatalf("Unmarshal: expected 'foo' but found '%s'", a.Any)
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ package xml
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
@ -42,20 +44,26 @@ type printer struct {
|
|||
// elements containing the data.
|
||||
//
|
||||
// The name for the XML elements is taken from, in order of preference:
|
||||
// - the tag on an XMLName field, if the data is a struct
|
||||
// - the value of an XMLName field of type xml.Name
|
||||
// - the tag on the XMLName field, if the data is a struct
|
||||
// - the value of the XMLName field of type xml.Name
|
||||
// - the tag of the struct field used to obtain the data
|
||||
// - the name of the struct field used to obtain the data
|
||||
// - the name '???'.
|
||||
// - the name of the marshalled type
|
||||
//
|
||||
// The XML element for a struct contains marshalled elements for each of the
|
||||
// exported fields of the struct, with these exceptions:
|
||||
// - the XMLName field, described above, is omitted.
|
||||
// - a field with tag "attr" becomes an attribute in the XML element.
|
||||
// - a field with tag "chardata" is written as character data,
|
||||
// not as an XML element.
|
||||
// - a field with tag "innerxml" is written verbatim,
|
||||
// not subject to the usual marshalling procedure.
|
||||
// - a field with tag "name,attr" becomes an attribute with
|
||||
// the given name in the XML element.
|
||||
// - a field with tag ",attr" becomes an attribute with the
|
||||
// field name in the in the XML element.
|
||||
// - a field with tag ",chardata" is written as character data,
|
||||
// not as an XML element.
|
||||
// - a field with tag ",innerxml" is written verbatim, not subject
|
||||
// to the usual marshalling procedure.
|
||||
// - a field with tag ",comment" is written as an XML comment, not
|
||||
// subject to the usual marshalling procedure. It must not contain
|
||||
// the "--" string within it.
|
||||
//
|
||||
// If a field uses a tag "a>b>c", then the element c will be nested inside
|
||||
// parent elements a and b. Fields that appear next to each other that name
|
||||
|
@ -63,17 +71,18 @@ type printer struct {
|
|||
//
|
||||
// type Result struct {
|
||||
// XMLName xml.Name `xml:"result"`
|
||||
// Id int `xml:"id,attr"`
|
||||
// FirstName string `xml:"person>name>first"`
|
||||
// LastName string `xml:"person>name>last"`
|
||||
// Age int `xml:"person>age"`
|
||||
// }
|
||||
//
|
||||
// xml.Marshal(w, &Result{FirstName: "John", LastName: "Doe", Age: 42})
|
||||
// xml.Marshal(w, &Result{Id: 13, FirstName: "John", LastName: "Doe", Age: 42})
|
||||
//
|
||||
// would be marshalled as:
|
||||
//
|
||||
// <result>
|
||||
// <person>
|
||||
// <person id="13">
|
||||
// <name>
|
||||
// <first>John</first>
|
||||
// <last>Doe</last>
|
||||
|
@ -85,12 +94,12 @@ type printer struct {
|
|||
// Marshal will return an error if asked to marshal a channel, function, or map.
|
||||
func Marshal(w io.Writer, v interface{}) (err error) {
|
||||
p := &printer{bufio.NewWriter(w)}
|
||||
err = p.marshalValue(reflect.ValueOf(v), "???")
|
||||
err = p.marshalValue(reflect.ValueOf(v), nil)
|
||||
p.Flush()
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *printer) marshalValue(val reflect.Value, name string) error {
|
||||
func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo) error {
|
||||
if !val.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
@ -115,58 +124,75 @@ func (p *printer) marshalValue(val reflect.Value, name string) error {
|
|||
if val.IsNil() {
|
||||
return nil
|
||||
}
|
||||
return p.marshalValue(val.Elem(), name)
|
||||
return p.marshalValue(val.Elem(), finfo)
|
||||
}
|
||||
|
||||
// Slices and arrays iterate over the elements. They do not have an enclosing tag.
|
||||
if (kind == reflect.Slice || kind == reflect.Array) && typ.Elem().Kind() != reflect.Uint8 {
|
||||
for i, n := 0, val.Len(); i < n; i++ {
|
||||
if err := p.marshalValue(val.Index(i), name); err != nil {
|
||||
if err := p.marshalValue(val.Index(i), finfo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find XML name
|
||||
xmlns := ""
|
||||
if kind == reflect.Struct {
|
||||
if f, ok := typ.FieldByName("XMLName"); ok {
|
||||
if tag := f.Tag.Get("xml"); tag != "" {
|
||||
if i := strings.Index(tag, " "); i >= 0 {
|
||||
xmlns, name = tag[:i], tag[i+1:]
|
||||
} else {
|
||||
name = tag
|
||||
}
|
||||
} else if v, ok := val.FieldByIndex(f.Index).Interface().(Name); ok && v.Local != "" {
|
||||
xmlns, name = v.Space, v.Local
|
||||
}
|
||||
tinfo, err := getTypeInfo(typ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Precedence for the XML element name is:
|
||||
// 1. XMLName field in underlying struct;
|
||||
// 2. field name/tag in the struct field; and
|
||||
// 3. type name
|
||||
var xmlns, name string
|
||||
if tinfo.xmlname != nil {
|
||||
xmlname := tinfo.xmlname
|
||||
if xmlname.name != "" {
|
||||
xmlns, name = xmlname.xmlns, xmlname.name
|
||||
} else if v, ok := val.FieldByIndex(xmlname.idx).Interface().(Name); ok && v.Local != "" {
|
||||
xmlns, name = v.Space, v.Local
|
||||
}
|
||||
}
|
||||
if name == "" && finfo != nil {
|
||||
xmlns, name = finfo.xmlns, finfo.name
|
||||
}
|
||||
if name == "" {
|
||||
name = typ.Name()
|
||||
if name == "" {
|
||||
return &UnsupportedTypeError{typ}
|
||||
}
|
||||
}
|
||||
|
||||
p.WriteByte('<')
|
||||
p.WriteString(name)
|
||||
|
||||
// Attributes
|
||||
if kind == reflect.Struct {
|
||||
if len(xmlns) > 0 {
|
||||
p.WriteString(` xmlns="`)
|
||||
Escape(p, []byte(xmlns))
|
||||
p.WriteByte('"')
|
||||
}
|
||||
if xmlns != "" {
|
||||
p.WriteString(` xmlns="`)
|
||||
// TODO: EscapeString, to avoid the allocation.
|
||||
Escape(p, []byte(xmlns))
|
||||
p.WriteByte('"')
|
||||
}
|
||||
|
||||
for i, n := 0, typ.NumField(); i < n; i++ {
|
||||
if f := typ.Field(i); f.PkgPath == "" && f.Tag.Get("xml") == "attr" {
|
||||
if f.Type.Kind() == reflect.String {
|
||||
if str := val.Field(i).String(); str != "" {
|
||||
p.WriteByte(' ')
|
||||
p.WriteString(strings.ToLower(f.Name))
|
||||
p.WriteString(`="`)
|
||||
Escape(p, []byte(str))
|
||||
p.WriteByte('"')
|
||||
}
|
||||
}
|
||||
}
|
||||
// Attributes
|
||||
for i := range tinfo.fields {
|
||||
finfo := &tinfo.fields[i]
|
||||
if finfo.flags&fAttr == 0 {
|
||||
continue
|
||||
}
|
||||
var str string
|
||||
if fv := val.FieldByIndex(finfo.idx); fv.Kind() == reflect.String {
|
||||
str = fv.String()
|
||||
} else {
|
||||
str = fmt.Sprint(fv.Interface())
|
||||
}
|
||||
if str != "" {
|
||||
p.WriteByte(' ')
|
||||
p.WriteString(finfo.name)
|
||||
p.WriteString(`="`)
|
||||
Escape(p, []byte(str))
|
||||
p.WriteByte('"')
|
||||
}
|
||||
}
|
||||
p.WriteByte('>')
|
||||
|
@ -194,58 +220,9 @@ func (p *printer) marshalValue(val reflect.Value, name string) error {
|
|||
bytes := val.Interface().([]byte)
|
||||
Escape(p, bytes)
|
||||
case reflect.Struct:
|
||||
s := parentStack{printer: p}
|
||||
for i, n := 0, val.NumField(); i < n; i++ {
|
||||
if f := typ.Field(i); f.Name != "XMLName" && f.PkgPath == "" {
|
||||
name := f.Name
|
||||
vf := val.Field(i)
|
||||
switch tag := f.Tag.Get("xml"); tag {
|
||||
case "":
|
||||
s.trim(nil)
|
||||
case "chardata":
|
||||
if tk := f.Type.Kind(); tk == reflect.String {
|
||||
Escape(p, []byte(vf.String()))
|
||||
} else if tk == reflect.Slice {
|
||||
if elem, ok := vf.Interface().([]byte); ok {
|
||||
Escape(p, elem)
|
||||
}
|
||||
}
|
||||
continue
|
||||
case "innerxml":
|
||||
iface := vf.Interface()
|
||||
switch raw := iface.(type) {
|
||||
case []byte:
|
||||
p.Write(raw)
|
||||
continue
|
||||
case string:
|
||||
p.WriteString(raw)
|
||||
continue
|
||||
}
|
||||
case "attr":
|
||||
continue
|
||||
default:
|
||||
parents := strings.Split(tag, ">")
|
||||
if len(parents) == 1 {
|
||||
parents, name = nil, tag
|
||||
} else {
|
||||
parents, name = parents[:len(parents)-1], parents[len(parents)-1]
|
||||
if parents[0] == "" {
|
||||
parents[0] = f.Name
|
||||
}
|
||||
}
|
||||
|
||||
s.trim(parents)
|
||||
if !(vf.Kind() == reflect.Ptr || vf.Kind() == reflect.Interface) || !vf.IsNil() {
|
||||
s.push(parents[len(s.stack):])
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.marshalValue(vf, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := p.marshalStruct(tinfo, val); err != nil {
|
||||
return err
|
||||
}
|
||||
s.trim(nil)
|
||||
default:
|
||||
return &UnsupportedTypeError{typ}
|
||||
}
|
||||
|
@ -258,6 +235,94 @@ func (p *printer) marshalValue(val reflect.Value, name string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var ddBytes = []byte("--")
|
||||
|
||||
func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
|
||||
s := parentStack{printer: p}
|
||||
for i := range tinfo.fields {
|
||||
finfo := &tinfo.fields[i]
|
||||
if finfo.flags&(fAttr|fAny) != 0 {
|
||||
continue
|
||||
}
|
||||
vf := val.FieldByIndex(finfo.idx)
|
||||
switch finfo.flags & fMode {
|
||||
case fCharData:
|
||||
switch vf.Kind() {
|
||||
case reflect.String:
|
||||
Escape(p, []byte(vf.String()))
|
||||
case reflect.Slice:
|
||||
if elem, ok := vf.Interface().([]byte); ok {
|
||||
Escape(p, elem)
|
||||
}
|
||||
}
|
||||
continue
|
||||
|
||||
case fComment:
|
||||
k := vf.Kind()
|
||||
if !(k == reflect.String || k == reflect.Slice && vf.Type().Elem().Kind() == reflect.Uint8) {
|
||||
return fmt.Errorf("xml: bad type for comment field of %s", val.Type())
|
||||
}
|
||||
if vf.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
p.WriteString("<!--")
|
||||
dashDash := false
|
||||
dashLast := false
|
||||
switch k {
|
||||
case reflect.String:
|
||||
s := vf.String()
|
||||
dashDash = strings.Index(s, "--") >= 0
|
||||
dashLast = s[len(s)-1] == '-'
|
||||
if !dashDash {
|
||||
p.WriteString(s)
|
||||
}
|
||||
case reflect.Slice:
|
||||
b := vf.Bytes()
|
||||
dashDash = bytes.Index(b, ddBytes) >= 0
|
||||
dashLast = b[len(b)-1] == '-'
|
||||
if !dashDash {
|
||||
p.Write(b)
|
||||
}
|
||||
default:
|
||||
panic("can't happen")
|
||||
}
|
||||
if dashDash {
|
||||
return fmt.Errorf(`xml: comments must not contain "--"`)
|
||||
}
|
||||
if dashLast {
|
||||
// "--->" is invalid grammar. Make it "- -->"
|
||||
p.WriteByte(' ')
|
||||
}
|
||||
p.WriteString("-->")
|
||||
continue
|
||||
|
||||
case fInnerXml:
|
||||
iface := vf.Interface()
|
||||
switch raw := iface.(type) {
|
||||
case []byte:
|
||||
p.Write(raw)
|
||||
continue
|
||||
case string:
|
||||
p.WriteString(raw)
|
||||
continue
|
||||
}
|
||||
|
||||
case fElement:
|
||||
s.trim(finfo.parents)
|
||||
if len(finfo.parents) > len(s.stack) {
|
||||
if vf.Kind() != reflect.Ptr && vf.Kind() != reflect.Interface || !vf.IsNil() {
|
||||
s.push(finfo.parents[len(s.stack):])
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := p.marshalValue(vf, finfo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.trim(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
type parentStack struct {
|
||||
*printer
|
||||
stack []string
|
||||
|
|
|
@ -25,10 +25,10 @@ type Passenger struct {
|
|||
}
|
||||
|
||||
type Ship struct {
|
||||
XMLName Name `xml:"spaceship"`
|
||||
XMLName struct{} `xml:"spaceship"`
|
||||
|
||||
Name string `xml:"attr"`
|
||||
Pilot string `xml:"attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Pilot string `xml:"pilot,attr"`
|
||||
Drive DriveType `xml:"drive"`
|
||||
Age uint `xml:"age"`
|
||||
Passenger []*Passenger `xml:"passenger"`
|
||||
|
@ -44,48 +44,50 @@ func (rx RawXML) MarshalXML() ([]byte, error) {
|
|||
type NamedType string
|
||||
|
||||
type Port struct {
|
||||
XMLName Name `xml:"port"`
|
||||
Type string `xml:"attr"`
|
||||
Number string `xml:"chardata"`
|
||||
XMLName struct{} `xml:"port"`
|
||||
Type string `xml:"type,attr"`
|
||||
Comment string `xml:",comment"`
|
||||
Number string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
XMLName Name `xml:"domain"`
|
||||
Country string `xml:"attr"`
|
||||
Name []byte `xml:"chardata"`
|
||||
XMLName struct{} `xml:"domain"`
|
||||
Country string `xml:",attr"`
|
||||
Name []byte `xml:",chardata"`
|
||||
Comment []byte `xml:",comment"`
|
||||
}
|
||||
|
||||
type Book struct {
|
||||
XMLName Name `xml:"book"`
|
||||
Title string `xml:"chardata"`
|
||||
XMLName struct{} `xml:"book"`
|
||||
Title string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type SecretAgent struct {
|
||||
XMLName Name `xml:"agent"`
|
||||
Handle string `xml:"attr"`
|
||||
XMLName struct{} `xml:"agent"`
|
||||
Handle string `xml:"handle,attr"`
|
||||
Identity string
|
||||
Obfuscate string `xml:"innerxml"`
|
||||
Obfuscate string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type NestedItems struct {
|
||||
XMLName Name `xml:"result"`
|
||||
XMLName struct{} `xml:"result"`
|
||||
Items []string `xml:">item"`
|
||||
Item1 []string `xml:"Items>item1"`
|
||||
}
|
||||
|
||||
type NestedOrder struct {
|
||||
XMLName Name `xml:"result"`
|
||||
Field1 string `xml:"parent>c"`
|
||||
Field2 string `xml:"parent>b"`
|
||||
Field3 string `xml:"parent>a"`
|
||||
XMLName struct{} `xml:"result"`
|
||||
Field1 string `xml:"parent>c"`
|
||||
Field2 string `xml:"parent>b"`
|
||||
Field3 string `xml:"parent>a"`
|
||||
}
|
||||
|
||||
type MixedNested struct {
|
||||
XMLName Name `xml:"result"`
|
||||
A string `xml:"parent1>a"`
|
||||
B string `xml:"b"`
|
||||
C string `xml:"parent1>parent2>c"`
|
||||
D string `xml:"parent1>d"`
|
||||
XMLName struct{} `xml:"result"`
|
||||
A string `xml:"parent1>a"`
|
||||
B string `xml:"b"`
|
||||
C string `xml:"parent1>parent2>c"`
|
||||
D string `xml:"parent1>d"`
|
||||
}
|
||||
|
||||
type NilTest struct {
|
||||
|
@ -95,62 +97,165 @@ type NilTest struct {
|
|||
}
|
||||
|
||||
type Service struct {
|
||||
XMLName Name `xml:"service"`
|
||||
Domain *Domain `xml:"host>domain"`
|
||||
Port *Port `xml:"host>port"`
|
||||
XMLName struct{} `xml:"service"`
|
||||
Domain *Domain `xml:"host>domain"`
|
||||
Port *Port `xml:"host>port"`
|
||||
Extra1 interface{}
|
||||
Extra2 interface{} `xml:"host>extra2"`
|
||||
}
|
||||
|
||||
var nilStruct *Ship
|
||||
|
||||
type EmbedA struct {
|
||||
EmbedC
|
||||
EmbedB EmbedB
|
||||
FieldA string
|
||||
}
|
||||
|
||||
type EmbedB struct {
|
||||
FieldB string
|
||||
EmbedC
|
||||
}
|
||||
|
||||
type EmbedC struct {
|
||||
FieldA1 string `xml:"FieldA>A1"`
|
||||
FieldA2 string `xml:"FieldA>A2"`
|
||||
FieldB string
|
||||
FieldC string
|
||||
}
|
||||
|
||||
type NameCasing struct {
|
||||
XMLName struct{} `xml:"casing"`
|
||||
Xy string
|
||||
XY string
|
||||
XyA string `xml:"Xy,attr"`
|
||||
XYA string `xml:"XY,attr"`
|
||||
}
|
||||
|
||||
type NamePrecedence struct {
|
||||
XMLName Name `xml:"Parent"`
|
||||
FromTag XMLNameWithoutTag `xml:"InTag"`
|
||||
FromNameVal XMLNameWithoutTag
|
||||
FromNameTag XMLNameWithTag
|
||||
InFieldName string
|
||||
}
|
||||
|
||||
type XMLNameWithTag struct {
|
||||
XMLName Name `xml:"InXMLNameTag"`
|
||||
Value string ",chardata"
|
||||
}
|
||||
|
||||
type XMLNameWithoutTag struct {
|
||||
XMLName Name
|
||||
Value string ",chardata"
|
||||
}
|
||||
|
||||
type AttrTest struct {
|
||||
Int int `xml:",attr"`
|
||||
Lower int `xml:"int,attr"`
|
||||
Float float64 `xml:",attr"`
|
||||
Uint8 uint8 `xml:",attr"`
|
||||
Bool bool `xml:",attr"`
|
||||
Str string `xml:",attr"`
|
||||
}
|
||||
|
||||
type AnyTest struct {
|
||||
XMLName struct{} `xml:"a"`
|
||||
Nested string `xml:"nested>value"`
|
||||
AnyField AnyHolder `xml:",any"`
|
||||
}
|
||||
|
||||
type AnyHolder struct {
|
||||
XMLName Name
|
||||
XML string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type RecurseA struct {
|
||||
A string
|
||||
B *RecurseB
|
||||
}
|
||||
|
||||
type RecurseB struct {
|
||||
A *RecurseA
|
||||
B string
|
||||
}
|
||||
|
||||
type Plain struct {
|
||||
V interface{}
|
||||
}
|
||||
|
||||
// Unless explicitly stated as such (or *Plain), all of the
|
||||
// tests below are two-way tests. When introducing new tests,
|
||||
// please try to make them two-way as well to ensure that
|
||||
// marshalling and unmarshalling are as symmetrical as feasible.
|
||||
var marshalTests = []struct {
|
||||
Value interface{}
|
||||
ExpectXML string
|
||||
Value interface{}
|
||||
ExpectXML string
|
||||
MarshalOnly bool
|
||||
UnmarshalOnly bool
|
||||
}{
|
||||
// Test nil marshals to nothing
|
||||
{Value: nil, ExpectXML: ``},
|
||||
{Value: nilStruct, ExpectXML: ``},
|
||||
{Value: nil, ExpectXML: ``, MarshalOnly: true},
|
||||
{Value: nilStruct, ExpectXML: ``, MarshalOnly: true},
|
||||
|
||||
// Test value types (no tag name, so ???)
|
||||
{Value: true, ExpectXML: `<???>true</???>`},
|
||||
{Value: int(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: int8(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: int16(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: int32(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: uint(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: uint8(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: uint16(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: uint32(42), ExpectXML: `<???>42</???>`},
|
||||
{Value: float32(1.25), ExpectXML: `<???>1.25</???>`},
|
||||
{Value: float64(1.25), ExpectXML: `<???>1.25</???>`},
|
||||
{Value: uintptr(0xFFDD), ExpectXML: `<???>65501</???>`},
|
||||
{Value: "gopher", ExpectXML: `<???>gopher</???>`},
|
||||
{Value: []byte("gopher"), ExpectXML: `<???>gopher</???>`},
|
||||
{Value: "</>", ExpectXML: `<???></></???>`},
|
||||
{Value: []byte("</>"), ExpectXML: `<???></></???>`},
|
||||
{Value: [3]byte{'<', '/', '>'}, ExpectXML: `<???></></???>`},
|
||||
{Value: NamedType("potato"), ExpectXML: `<???>potato</???>`},
|
||||
{Value: []int{1, 2, 3}, ExpectXML: `<???>1</???><???>2</???><???>3</???>`},
|
||||
{Value: [3]int{1, 2, 3}, ExpectXML: `<???>1</???><???>2</???><???>3</???>`},
|
||||
// Test value types
|
||||
{Value: &Plain{true}, ExpectXML: `<Plain><V>true</V></Plain>`},
|
||||
{Value: &Plain{false}, ExpectXML: `<Plain><V>false</V></Plain>`},
|
||||
{Value: &Plain{int(42)}, ExpectXML: `<Plain><V>42</V></Plain>`},
|
||||
{Value: &Plain{int8(42)}, ExpectXML: `<Plain><V>42</V></Plain>`},
|
||||
{Value: &Plain{int16(42)}, ExpectXML: `<Plain><V>42</V></Plain>`},
|
||||
{Value: &Plain{int32(42)}, ExpectXML: `<Plain><V>42</V></Plain>`},
|
||||
{Value: &Plain{uint(42)}, ExpectXML: `<Plain><V>42</V></Plain>`},
|
||||
{Value: &Plain{uint8(42)}, ExpectXML: `<Plain><V>42</V></Plain>`},
|
||||
{Value: &Plain{uint16(42)}, ExpectXML: `<Plain><V>42</V></Plain>`},
|
||||
{Value: &Plain{uint32(42)}, ExpectXML: `<Plain><V>42</V></Plain>`},
|
||||
{Value: &Plain{float32(1.25)}, ExpectXML: `<Plain><V>1.25</V></Plain>`},
|
||||
{Value: &Plain{float64(1.25)}, ExpectXML: `<Plain><V>1.25</V></Plain>`},
|
||||
{Value: &Plain{uintptr(0xFFDD)}, ExpectXML: `<Plain><V>65501</V></Plain>`},
|
||||
{Value: &Plain{"gopher"}, ExpectXML: `<Plain><V>gopher</V></Plain>`},
|
||||
{Value: &Plain{[]byte("gopher")}, ExpectXML: `<Plain><V>gopher</V></Plain>`},
|
||||
{Value: &Plain{"</>"}, ExpectXML: `<Plain><V></></V></Plain>`},
|
||||
{Value: &Plain{[]byte("</>")}, ExpectXML: `<Plain><V></></V></Plain>`},
|
||||
{Value: &Plain{[3]byte{'<', '/', '>'}}, ExpectXML: `<Plain><V></></V></Plain>`},
|
||||
{Value: &Plain{NamedType("potato")}, ExpectXML: `<Plain><V>potato</V></Plain>`},
|
||||
{Value: &Plain{[]int{1, 2, 3}}, ExpectXML: `<Plain><V>1</V><V>2</V><V>3</V></Plain>`},
|
||||
{Value: &Plain{[3]int{1, 2, 3}}, ExpectXML: `<Plain><V>1</V><V>2</V><V>3</V></Plain>`},
|
||||
|
||||
// Test innerxml
|
||||
{Value: RawXML("</>"), ExpectXML: `</>`},
|
||||
{
|
||||
Value: &SecretAgent{
|
||||
Handle: "007",
|
||||
Identity: "James Bond",
|
||||
Obfuscate: "<redacted/>",
|
||||
},
|
||||
//ExpectXML: `<agent handle="007"><redacted/></agent>`,
|
||||
ExpectXML: `<agent handle="007"><Identity>James Bond</Identity><redacted/></agent>`,
|
||||
ExpectXML: `<agent handle="007"><Identity>James Bond</Identity><redacted/></agent>`,
|
||||
MarshalOnly: true,
|
||||
},
|
||||
{
|
||||
Value: &SecretAgent{
|
||||
Handle: "007",
|
||||
Identity: "James Bond",
|
||||
Obfuscate: "<Identity>James Bond</Identity><redacted/>",
|
||||
},
|
||||
ExpectXML: `<agent handle="007"><Identity>James Bond</Identity><redacted/></agent>`,
|
||||
UnmarshalOnly: true,
|
||||
},
|
||||
|
||||
// Test marshaller interface
|
||||
{
|
||||
Value: RawXML("</>"),
|
||||
ExpectXML: `</>`,
|
||||
MarshalOnly: true,
|
||||
},
|
||||
|
||||
// Test structs
|
||||
{Value: &Port{Type: "ssl", Number: "443"}, ExpectXML: `<port type="ssl">443</port>`},
|
||||
{Value: &Port{Number: "443"}, ExpectXML: `<port>443</port>`},
|
||||
{Value: &Port{Type: "<unix>"}, ExpectXML: `<port type="<unix>"></port>`},
|
||||
{Value: &Port{Number: "443", Comment: "https"}, ExpectXML: `<port><!--https-->443</port>`},
|
||||
{Value: &Port{Number: "443", Comment: "add space-"}, ExpectXML: `<port><!--add space- -->443</port>`, MarshalOnly: true},
|
||||
{Value: &Domain{Name: []byte("google.com&friends")}, ExpectXML: `<domain>google.com&friends</domain>`},
|
||||
{Value: &Domain{Name: []byte("google.com"), Comment: []byte(" &friends ")}, ExpectXML: `<domain>google.com<!-- &friends --></domain>`},
|
||||
{Value: &Book{Title: "Pride & Prejudice"}, ExpectXML: `<book>Pride & Prejudice</book>`},
|
||||
{Value: atomValue, ExpectXML: atomXml},
|
||||
{
|
||||
|
@ -203,16 +308,25 @@ var marshalTests = []struct {
|
|||
`</passenger>` +
|
||||
`</spaceship>`,
|
||||
},
|
||||
|
||||
// Test a>b
|
||||
{
|
||||
Value: NestedItems{Items: []string{}, Item1: []string{}},
|
||||
Value: &NestedItems{Items: nil, Item1: nil},
|
||||
ExpectXML: `<result>` +
|
||||
`<Items>` +
|
||||
`</Items>` +
|
||||
`</result>`,
|
||||
},
|
||||
{
|
||||
Value: NestedItems{Items: []string{}, Item1: []string{"A"}},
|
||||
Value: &NestedItems{Items: []string{}, Item1: []string{}},
|
||||
ExpectXML: `<result>` +
|
||||
`<Items>` +
|
||||
`</Items>` +
|
||||
`</result>`,
|
||||
MarshalOnly: true,
|
||||
},
|
||||
{
|
||||
Value: &NestedItems{Items: nil, Item1: []string{"A"}},
|
||||
ExpectXML: `<result>` +
|
||||
`<Items>` +
|
||||
`<item1>A</item1>` +
|
||||
|
@ -220,7 +334,7 @@ var marshalTests = []struct {
|
|||
`</result>`,
|
||||
},
|
||||
{
|
||||
Value: NestedItems{Items: []string{"A", "B"}, Item1: []string{}},
|
||||
Value: &NestedItems{Items: []string{"A", "B"}, Item1: nil},
|
||||
ExpectXML: `<result>` +
|
||||
`<Items>` +
|
||||
`<item>A</item>` +
|
||||
|
@ -229,7 +343,7 @@ var marshalTests = []struct {
|
|||
`</result>`,
|
||||
},
|
||||
{
|
||||
Value: NestedItems{Items: []string{"A", "B"}, Item1: []string{"C"}},
|
||||
Value: &NestedItems{Items: []string{"A", "B"}, Item1: []string{"C"}},
|
||||
ExpectXML: `<result>` +
|
||||
`<Items>` +
|
||||
`<item>A</item>` +
|
||||
|
@ -239,7 +353,7 @@ var marshalTests = []struct {
|
|||
`</result>`,
|
||||
},
|
||||
{
|
||||
Value: NestedOrder{Field1: "C", Field2: "B", Field3: "A"},
|
||||
Value: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"},
|
||||
ExpectXML: `<result>` +
|
||||
`<parent>` +
|
||||
`<c>C</c>` +
|
||||
|
@ -249,16 +363,17 @@ var marshalTests = []struct {
|
|||
`</result>`,
|
||||
},
|
||||
{
|
||||
Value: NilTest{A: "A", B: nil, C: "C"},
|
||||
ExpectXML: `<???>` +
|
||||
Value: &NilTest{A: "A", B: nil, C: "C"},
|
||||
ExpectXML: `<NilTest>` +
|
||||
`<parent1>` +
|
||||
`<parent2><a>A</a></parent2>` +
|
||||
`<parent2><c>C</c></parent2>` +
|
||||
`</parent1>` +
|
||||
`</???>`,
|
||||
`</NilTest>`,
|
||||
MarshalOnly: true, // Uses interface{}
|
||||
},
|
||||
{
|
||||
Value: MixedNested{A: "A", B: "B", C: "C", D: "D"},
|
||||
Value: &MixedNested{A: "A", B: "B", C: "C", D: "D"},
|
||||
ExpectXML: `<result>` +
|
||||
`<parent1><a>A</a></parent1>` +
|
||||
`<b>B</b>` +
|
||||
|
@ -269,32 +384,154 @@ var marshalTests = []struct {
|
|||
`</result>`,
|
||||
},
|
||||
{
|
||||
Value: Service{Port: &Port{Number: "80"}},
|
||||
Value: &Service{Port: &Port{Number: "80"}},
|
||||
ExpectXML: `<service><host><port>80</port></host></service>`,
|
||||
},
|
||||
{
|
||||
Value: Service{},
|
||||
Value: &Service{},
|
||||
ExpectXML: `<service></service>`,
|
||||
},
|
||||
{
|
||||
Value: Service{Port: &Port{Number: "80"}, Extra1: "A", Extra2: "B"},
|
||||
Value: &Service{Port: &Port{Number: "80"}, Extra1: "A", Extra2: "B"},
|
||||
ExpectXML: `<service>` +
|
||||
`<host><port>80</port></host>` +
|
||||
`<Extra1>A</Extra1>` +
|
||||
`<host><extra2>B</extra2></host>` +
|
||||
`</service>`,
|
||||
MarshalOnly: true,
|
||||
},
|
||||
{
|
||||
Value: Service{Port: &Port{Number: "80"}, Extra2: "example"},
|
||||
Value: &Service{Port: &Port{Number: "80"}, Extra2: "example"},
|
||||
ExpectXML: `<service>` +
|
||||
`<host><port>80</port></host>` +
|
||||
`<host><extra2>example</extra2></host>` +
|
||||
`</service>`,
|
||||
MarshalOnly: true,
|
||||
},
|
||||
|
||||
// Test struct embedding
|
||||
{
|
||||
Value: &EmbedA{
|
||||
EmbedC: EmbedC{
|
||||
FieldA1: "", // Shadowed by A.A
|
||||
FieldA2: "", // Shadowed by A.A
|
||||
FieldB: "A.C.B",
|
||||
FieldC: "A.C.C",
|
||||
},
|
||||
EmbedB: EmbedB{
|
||||
FieldB: "A.B.B",
|
||||
EmbedC: EmbedC{
|
||||
FieldA1: "A.B.C.A1",
|
||||
FieldA2: "A.B.C.A2",
|
||||
FieldB: "", // Shadowed by A.B.B
|
||||
FieldC: "A.B.C.C",
|
||||
},
|
||||
},
|
||||
FieldA: "A.A",
|
||||
},
|
||||
ExpectXML: `<EmbedA>` +
|
||||
`<FieldB>A.C.B</FieldB>` +
|
||||
`<FieldC>A.C.C</FieldC>` +
|
||||
`<EmbedB>` +
|
||||
`<FieldB>A.B.B</FieldB>` +
|
||||
`<FieldA>` +
|
||||
`<A1>A.B.C.A1</A1>` +
|
||||
`<A2>A.B.C.A2</A2>` +
|
||||
`</FieldA>` +
|
||||
`<FieldC>A.B.C.C</FieldC>` +
|
||||
`</EmbedB>` +
|
||||
`<FieldA>A.A</FieldA>` +
|
||||
`</EmbedA>`,
|
||||
},
|
||||
|
||||
// Test that name casing matters
|
||||
{
|
||||
Value: &NameCasing{Xy: "mixed", XY: "upper", XyA: "mixedA", XYA: "upperA"},
|
||||
ExpectXML: `<casing Xy="mixedA" XY="upperA"><Xy>mixed</Xy><XY>upper</XY></casing>`,
|
||||
},
|
||||
|
||||
// Test the order in which the XML element name is chosen
|
||||
{
|
||||
Value: &NamePrecedence{
|
||||
FromTag: XMLNameWithoutTag{Value: "A"},
|
||||
FromNameVal: XMLNameWithoutTag{XMLName: Name{Local: "InXMLName"}, Value: "B"},
|
||||
FromNameTag: XMLNameWithTag{Value: "C"},
|
||||
InFieldName: "D",
|
||||
},
|
||||
ExpectXML: `<Parent>` +
|
||||
`<InTag><Value>A</Value></InTag>` +
|
||||
`<InXMLName><Value>B</Value></InXMLName>` +
|
||||
`<InXMLNameTag><Value>C</Value></InXMLNameTag>` +
|
||||
`<InFieldName>D</InFieldName>` +
|
||||
`</Parent>`,
|
||||
MarshalOnly: true,
|
||||
},
|
||||
{
|
||||
Value: &NamePrecedence{
|
||||
XMLName: Name{Local: "Parent"},
|
||||
FromTag: XMLNameWithoutTag{XMLName: Name{Local: "InTag"}, Value: "A"},
|
||||
FromNameVal: XMLNameWithoutTag{XMLName: Name{Local: "FromNameVal"}, Value: "B"},
|
||||
FromNameTag: XMLNameWithTag{XMLName: Name{Local: "InXMLNameTag"}, Value: "C"},
|
||||
InFieldName: "D",
|
||||
},
|
||||
ExpectXML: `<Parent>` +
|
||||
`<InTag><Value>A</Value></InTag>` +
|
||||
`<FromNameVal><Value>B</Value></FromNameVal>` +
|
||||
`<InXMLNameTag><Value>C</Value></InXMLNameTag>` +
|
||||
`<InFieldName>D</InFieldName>` +
|
||||
`</Parent>`,
|
||||
UnmarshalOnly: true,
|
||||
},
|
||||
|
||||
// Test attributes
|
||||
{
|
||||
Value: &AttrTest{
|
||||
Int: 8,
|
||||
Lower: 9,
|
||||
Float: 23.5,
|
||||
Uint8: 255,
|
||||
Bool: true,
|
||||
Str: "s",
|
||||
},
|
||||
ExpectXML: `<AttrTest Int="8" int="9" Float="23.5" Uint8="255" Bool="true" Str="s"></AttrTest>`,
|
||||
},
|
||||
|
||||
// Test ",any"
|
||||
{
|
||||
ExpectXML: `<a><nested><value>known</value></nested><other><sub>unknown</sub></other></a>`,
|
||||
Value: &AnyTest{
|
||||
Nested: "known",
|
||||
AnyField: AnyHolder{
|
||||
XMLName: Name{Local: "other"},
|
||||
XML: "<sub>unknown</sub>",
|
||||
},
|
||||
},
|
||||
UnmarshalOnly: true,
|
||||
},
|
||||
{
|
||||
Value: &AnyTest{Nested: "known", AnyField: AnyHolder{XML: "<unknown/>"}},
|
||||
ExpectXML: `<a><nested><value>known</value></nested></a>`,
|
||||
MarshalOnly: true,
|
||||
},
|
||||
|
||||
// Test recursive types.
|
||||
{
|
||||
Value: &RecurseA{
|
||||
A: "a1",
|
||||
B: &RecurseB{
|
||||
A: &RecurseA{"a2", nil},
|
||||
B: "b1",
|
||||
},
|
||||
},
|
||||
ExpectXML: `<RecurseA><A>a1</A><B><A><A>a2</A></A><B>b1</B></B></RecurseA>`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
for idx, test := range marshalTests {
|
||||
if test.UnmarshalOnly {
|
||||
continue
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := Marshal(buf, test.Value)
|
||||
if err != nil {
|
||||
|
@ -303,9 +540,9 @@ func TestMarshal(t *testing.T) {
|
|||
}
|
||||
if got, want := buf.String(), test.ExpectXML; got != want {
|
||||
if strings.Contains(want, "\n") {
|
||||
t.Errorf("#%d: marshal(%#v) - GOT:\n%s\nWANT:\n%s", idx, test.Value, got, want)
|
||||
t.Errorf("#%d: marshal(%#v):\nHAVE:\n%s\nWANT:\n%s", idx, test.Value, got, want)
|
||||
} else {
|
||||
t.Errorf("#%d: marshal(%#v) = %#q want %#q", idx, test.Value, got, want)
|
||||
t.Errorf("#%d: marshal(%#v):\nhave %#q\nwant %#q", idx, test.Value, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -334,6 +571,10 @@ var marshalErrorTests = []struct {
|
|||
Err: "xml: unsupported type: map[*xml.Ship]bool",
|
||||
Kind: reflect.Map,
|
||||
},
|
||||
{
|
||||
Value: &Domain{Comment: []byte("f--bar")},
|
||||
Err: `xml: comments must not contain "--"`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestMarshalErrors(t *testing.T) {
|
||||
|
@ -341,10 +582,12 @@ func TestMarshalErrors(t *testing.T) {
|
|||
buf := bytes.NewBuffer(nil)
|
||||
err := Marshal(buf, test.Value)
|
||||
if err == nil || err.Error() != test.Err {
|
||||
t.Errorf("#%d: marshal(%#v) = [error] %q, want %q", idx, test.Value, err, test.Err)
|
||||
t.Errorf("#%d: marshal(%#v) = [error] %v, want %v", idx, test.Value, err, test.Err)
|
||||
}
|
||||
if kind := err.(*UnsupportedTypeError).Type.Kind(); kind != test.Kind {
|
||||
t.Errorf("#%d: marshal(%#v) = [error kind] %s, want %s", idx, test.Value, kind, test.Kind)
|
||||
if test.Kind != reflect.Invalid {
|
||||
if kind := err.(*UnsupportedTypeError).Type.Kind(); kind != test.Kind {
|
||||
t.Errorf("#%d: marshal(%#v) = [error kind] %s, want %s", idx, test.Value, kind, test.Kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -352,39 +595,20 @@ func TestMarshalErrors(t *testing.T) {
|
|||
// Do invertibility testing on the various structures that we test
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
for i, test := range marshalTests {
|
||||
// Skip the nil pointers
|
||||
if i <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
var dest interface{}
|
||||
|
||||
switch test.Value.(type) {
|
||||
case *Ship, Ship:
|
||||
dest = &Ship{}
|
||||
case *Port, Port:
|
||||
dest = &Port{}
|
||||
case *Domain, Domain:
|
||||
dest = &Domain{}
|
||||
case *Feed, Feed:
|
||||
dest = &Feed{}
|
||||
default:
|
||||
if test.MarshalOnly {
|
||||
continue
|
||||
}
|
||||
if _, ok := test.Value.(*Plain); ok {
|
||||
continue
|
||||
}
|
||||
|
||||
vt := reflect.TypeOf(test.Value)
|
||||
dest := reflect.New(vt.Elem()).Interface()
|
||||
buffer := bytes.NewBufferString(test.ExpectXML)
|
||||
err := Unmarshal(buffer, dest)
|
||||
|
||||
// Don't compare XMLNames
|
||||
switch fix := dest.(type) {
|
||||
case *Ship:
|
||||
fix.XMLName = Name{}
|
||||
case *Port:
|
||||
fix.XMLName = Name{}
|
||||
case *Domain:
|
||||
fix.XMLName = Name{}
|
||||
case *Feed:
|
||||
fix.XMLName = Name{}
|
||||
fix.Author.InnerXML = ""
|
||||
for i := range fix.Entry {
|
||||
fix.Entry[i].Author.InnerXML = ""
|
||||
|
@ -394,30 +618,23 @@ func TestUnmarshal(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("#%d: unexpected error: %#v", i, err)
|
||||
} else if got, want := dest, test.Value; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("#%d: unmarshal(%q) = %#v, want %#v", i, test.ExpectXML, got, want)
|
||||
t.Errorf("#%d: unmarshal(%q):\nhave %#v\nwant %#v", i, test.ExpectXML, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshal(b *testing.B) {
|
||||
idx := len(marshalTests) - 1
|
||||
test := marshalTests[idx]
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for i := 0; i < b.N; i++ {
|
||||
Marshal(buf, test.Value)
|
||||
Marshal(buf, atomValue)
|
||||
buf.Truncate(0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshal(b *testing.B) {
|
||||
idx := len(marshalTests) - 1
|
||||
test := marshalTests[idx]
|
||||
sm := &Ship{}
|
||||
xml := []byte(test.ExpectXML)
|
||||
|
||||
xml := []byte(atomXml)
|
||||
for i := 0; i < b.N; i++ {
|
||||
buffer := bytes.NewBuffer(xml)
|
||||
Unmarshal(buffer, sm)
|
||||
Unmarshal(buffer, &Feed{})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,10 @@ package xml
|
|||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// BUG(rsc): Mapping between XML elements and data structures is inherently flawed:
|
||||
|
@ -31,7 +28,7 @@ import (
|
|||
// For example, given these definitions:
|
||||
//
|
||||
// type Email struct {
|
||||
// Where string `xml:"attr"`
|
||||
// Where string `xml:",attr"`
|
||||
// Addr string
|
||||
// }
|
||||
//
|
||||
|
@ -64,7 +61,8 @@ import (
|
|||
//
|
||||
// via Unmarshal(r, &result) is equivalent to assigning
|
||||
//
|
||||
// r = Result{xml.Name{"", "result"},
|
||||
// r = Result{
|
||||
// xml.Name{Local: "result"},
|
||||
// "Grace R. Emlin", // name
|
||||
// "phone", // no phone given
|
||||
// []Email{
|
||||
|
@ -87,9 +85,9 @@ import (
|
|||
// In the rules, the tag of a field refers to the value associated with the
|
||||
// key 'xml' in the struct field's tag (see the example above).
|
||||
//
|
||||
// * If the struct has a field of type []byte or string with tag "innerxml",
|
||||
// Unmarshal accumulates the raw XML nested inside the element
|
||||
// in that field. The rest of the rules still apply.
|
||||
// * If the struct has a field of type []byte or string with tag
|
||||
// ",innerxml", Unmarshal accumulates the raw XML nested inside the
|
||||
// element in that field. The rest of the rules still apply.
|
||||
//
|
||||
// * If the struct has a field named XMLName of type xml.Name,
|
||||
// Unmarshal records the element name in that field.
|
||||
|
@ -100,8 +98,9 @@ import (
|
|||
// returns an error.
|
||||
//
|
||||
// * If the XML element has an attribute whose name matches a
|
||||
// struct field of type string with tag "attr", Unmarshal records
|
||||
// the attribute value in that field.
|
||||
// struct field name with an associated tag containing ",attr" or
|
||||
// the explicit name in a struct field tag of the form "name,attr",
|
||||
// Unmarshal records the attribute value in that field.
|
||||
//
|
||||
// * If the XML element contains character data, that data is
|
||||
// accumulated in the first struct field that has tag "chardata".
|
||||
|
@ -109,23 +108,30 @@ import (
|
|||
// If there is no such field, the character data is discarded.
|
||||
//
|
||||
// * If the XML element contains comments, they are accumulated in
|
||||
// the first struct field that has tag "comments". The struct
|
||||
// the first struct field that has tag ",comments". The struct
|
||||
// field may have type []byte or string. If there is no such
|
||||
// field, the comments are discarded.
|
||||
//
|
||||
// * If the XML element contains a sub-element whose name matches
|
||||
// the prefix of a tag formatted as "a>b>c", unmarshal
|
||||
// the prefix of a tag formatted as "a" or "a>b>c", unmarshal
|
||||
// will descend into the XML structure looking for elements with the
|
||||
// given names, and will map the innermost elements to that struct field.
|
||||
// A tag starting with ">" is equivalent to one starting
|
||||
// given names, and will map the innermost elements to that struct
|
||||
// field. A tag starting with ">" is equivalent to one starting
|
||||
// with the field name followed by ">".
|
||||
//
|
||||
// * If the XML element contains a sub-element whose name
|
||||
// matches a field whose tag is neither "attr" nor "chardata",
|
||||
// Unmarshal maps the sub-element to that struct field.
|
||||
// Otherwise, if the struct has a field named Any, unmarshal
|
||||
// * If the XML element contains a sub-element whose name matches
|
||||
// a struct field's XMLName tag and the struct field has no
|
||||
// explicit name tag as per the previous rule, unmarshal maps
|
||||
// the sub-element to that struct field.
|
||||
//
|
||||
// * If the XML element contains a sub-element whose name matches a
|
||||
// field without any mode flags (",attr", ",chardata", etc), Unmarshal
|
||||
// maps the sub-element to that struct field.
|
||||
//
|
||||
// * If the XML element contains a sub-element that hasn't matched any
|
||||
// of the above rules and the struct has a field with tag ",any",
|
||||
// unmarshal maps the sub-element to that struct field.
|
||||
//
|
||||
// Unmarshal maps an XML element to a string or []byte by saving the
|
||||
// concatenation of that element's character data in the string or
|
||||
// []byte.
|
||||
|
@ -169,18 +175,6 @@ type UnmarshalError string
|
|||
|
||||
func (e UnmarshalError) Error() string { return string(e) }
|
||||
|
||||
// A TagPathError represents an error in the unmarshalling process
|
||||
// caused by the use of field tags with conflicting paths.
|
||||
type TagPathError struct {
|
||||
Struct reflect.Type
|
||||
Field1, Tag1 string
|
||||
Field2, Tag2 string
|
||||
}
|
||||
|
||||
func (e *TagPathError) Error() string {
|
||||
return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2)
|
||||
}
|
||||
|
||||
// The Parser's Unmarshal method is like xml.Unmarshal
|
||||
// except that it can be passed a pointer to the initial start element,
|
||||
// useful when a client reads some raw XML tokens itself
|
||||
|
@ -195,26 +189,6 @@ func (p *Parser) Unmarshal(val interface{}, start *StartElement) error {
|
|||
return p.unmarshal(v.Elem(), start)
|
||||
}
|
||||
|
||||
// fieldName strips invalid characters from an XML name
|
||||
// to create a valid Go struct name. It also converts the
|
||||
// name to lower case letters.
|
||||
func fieldName(original string) string {
|
||||
|
||||
var i int
|
||||
//remove leading underscores, without exhausting all characters
|
||||
for i = 0; i < len(original)-1 && original[i] == '_'; i++ {
|
||||
}
|
||||
|
||||
return strings.Map(
|
||||
func(x rune) rune {
|
||||
if x == '_' || unicode.IsDigit(x) || unicode.IsLetter(x) {
|
||||
return unicode.ToLower(x)
|
||||
}
|
||||
return -1
|
||||
},
|
||||
original[i:])
|
||||
}
|
||||
|
||||
// Unmarshal a single XML element into val.
|
||||
func (p *Parser) unmarshal(val reflect.Value, start *StartElement) error {
|
||||
// Find start element if we need it.
|
||||
|
@ -246,15 +220,22 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) error {
|
|||
saveXML reflect.Value
|
||||
saveXMLIndex int
|
||||
saveXMLData []byte
|
||||
saveAny reflect.Value
|
||||
sv reflect.Value
|
||||
styp reflect.Type
|
||||
fieldPaths map[string]pathInfo
|
||||
tinfo *typeInfo
|
||||
err error
|
||||
)
|
||||
|
||||
switch v := val; v.Kind() {
|
||||
default:
|
||||
return errors.New("unknown type " + v.Type().String())
|
||||
|
||||
case reflect.Interface:
|
||||
// TODO: For now, simply ignore the field. In the near
|
||||
// future we may choose to unmarshal the start
|
||||
// element on it, if not nil.
|
||||
return p.Skip()
|
||||
|
||||
case reflect.Slice:
|
||||
typ := v.Type()
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
|
@ -288,75 +269,69 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) error {
|
|||
saveData = v
|
||||
|
||||
case reflect.Struct:
|
||||
if _, ok := v.Interface().(Name); ok {
|
||||
v.Set(reflect.ValueOf(start.Name))
|
||||
break
|
||||
}
|
||||
|
||||
sv = v
|
||||
typ := sv.Type()
|
||||
styp = typ
|
||||
// Assign name.
|
||||
if f, ok := typ.FieldByName("XMLName"); ok {
|
||||
// Validate element name.
|
||||
if tag := f.Tag.Get("xml"); tag != "" {
|
||||
ns := ""
|
||||
i := strings.LastIndex(tag, " ")
|
||||
if i >= 0 {
|
||||
ns, tag = tag[0:i], tag[i+1:]
|
||||
}
|
||||
if tag != start.Name.Local {
|
||||
return UnmarshalError("expected element type <" + tag + "> but have <" + start.Name.Local + ">")
|
||||
}
|
||||
if ns != "" && ns != start.Name.Space {
|
||||
e := "expected element <" + tag + "> in name space " + ns + " but have "
|
||||
if start.Name.Space == "" {
|
||||
e += "no name space"
|
||||
} else {
|
||||
e += start.Name.Space
|
||||
}
|
||||
return UnmarshalError(e)
|
||||
}
|
||||
}
|
||||
tinfo, err = getTypeInfo(typ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save
|
||||
v := sv.FieldByIndex(f.Index)
|
||||
if _, ok := v.Interface().(Name); ok {
|
||||
v.Set(reflect.ValueOf(start.Name))
|
||||
// Validate and assign element name.
|
||||
if tinfo.xmlname != nil {
|
||||
finfo := tinfo.xmlname
|
||||
if finfo.name != "" && finfo.name != start.Name.Local {
|
||||
return UnmarshalError("expected element type <" + finfo.name + "> but have <" + start.Name.Local + ">")
|
||||
}
|
||||
if finfo.xmlns != "" && finfo.xmlns != start.Name.Space {
|
||||
e := "expected element <" + finfo.name + "> in name space " + finfo.xmlns + " but have "
|
||||
if start.Name.Space == "" {
|
||||
e += "no name space"
|
||||
} else {
|
||||
e += start.Name.Space
|
||||
}
|
||||
return UnmarshalError(e)
|
||||
}
|
||||
fv := sv.FieldByIndex(finfo.idx)
|
||||
if _, ok := fv.Interface().(Name); ok {
|
||||
fv.Set(reflect.ValueOf(start.Name))
|
||||
}
|
||||
}
|
||||
|
||||
// Assign attributes.
|
||||
// Also, determine whether we need to save character data or comments.
|
||||
for i, n := 0, typ.NumField(); i < n; i++ {
|
||||
f := typ.Field(i)
|
||||
switch f.Tag.Get("xml") {
|
||||
case "attr":
|
||||
strv := sv.FieldByIndex(f.Index)
|
||||
for i := range tinfo.fields {
|
||||
finfo := &tinfo.fields[i]
|
||||
switch finfo.flags & fMode {
|
||||
case fAttr:
|
||||
strv := sv.FieldByIndex(finfo.idx)
|
||||
// Look for attribute.
|
||||
val := ""
|
||||
k := strings.ToLower(f.Name)
|
||||
for _, a := range start.Attr {
|
||||
if fieldName(a.Name.Local) == k {
|
||||
if a.Name.Local == finfo.name {
|
||||
val = a.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
copyValue(strv, []byte(val))
|
||||
|
||||
case "comment":
|
||||
if !saveComment.IsValid() {
|
||||
saveComment = sv.FieldByIndex(f.Index)
|
||||
}
|
||||
|
||||
case "chardata":
|
||||
case fCharData:
|
||||
if !saveData.IsValid() {
|
||||
saveData = sv.FieldByIndex(f.Index)
|
||||
saveData = sv.FieldByIndex(finfo.idx)
|
||||
}
|
||||
|
||||
case "innerxml":
|
||||
case fComment:
|
||||
if !saveComment.IsValid() {
|
||||
saveComment = sv.FieldByIndex(finfo.idx)
|
||||
}
|
||||
|
||||
case fAny:
|
||||
if !saveAny.IsValid() {
|
||||
saveAny = sv.FieldByIndex(finfo.idx)
|
||||
}
|
||||
|
||||
case fInnerXml:
|
||||
if !saveXML.IsValid() {
|
||||
saveXML = sv.FieldByIndex(f.Index)
|
||||
saveXML = sv.FieldByIndex(finfo.idx)
|
||||
if p.saved == nil {
|
||||
saveXMLIndex = 0
|
||||
p.saved = new(bytes.Buffer)
|
||||
|
@ -364,24 +339,6 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) error {
|
|||
saveXMLIndex = p.savedOffset()
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
if tag := f.Tag.Get("xml"); strings.Contains(tag, ">") {
|
||||
if fieldPaths == nil {
|
||||
fieldPaths = make(map[string]pathInfo)
|
||||
}
|
||||
path := strings.ToLower(tag)
|
||||
if strings.HasPrefix(tag, ">") {
|
||||
path = strings.ToLower(f.Name) + path
|
||||
}
|
||||
if strings.HasSuffix(tag, ">") {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
err := addFieldPath(sv, fieldPaths, path, f.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -400,44 +357,23 @@ Loop:
|
|||
}
|
||||
switch t := tok.(type) {
|
||||
case StartElement:
|
||||
// Sub-element.
|
||||
// Look up by tag name.
|
||||
consumed := false
|
||||
if sv.IsValid() {
|
||||
k := fieldName(t.Name.Local)
|
||||
|
||||
if fieldPaths != nil {
|
||||
if _, found := fieldPaths[k]; found {
|
||||
if err := p.unmarshalPaths(sv, fieldPaths, k, &t); err != nil {
|
||||
return err
|
||||
}
|
||||
continue Loop
|
||||
}
|
||||
consumed, err = p.unmarshalPath(tinfo, sv, nil, &t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
match := func(s string) bool {
|
||||
// check if the name matches ignoring case
|
||||
if strings.ToLower(s) != k {
|
||||
return false
|
||||
}
|
||||
// now check that it's public
|
||||
c, _ := utf8.DecodeRuneInString(s)
|
||||
return unicode.IsUpper(c)
|
||||
}
|
||||
|
||||
f, found := styp.FieldByNameFunc(match)
|
||||
if !found { // fall back to mop-up field named "Any"
|
||||
f, found = styp.FieldByName("Any")
|
||||
}
|
||||
if found {
|
||||
if err := p.unmarshal(sv.FieldByIndex(f.Index), &t); err != nil {
|
||||
if !consumed && saveAny.IsValid() {
|
||||
consumed = true
|
||||
if err := p.unmarshal(saveAny, &t); err != nil {
|
||||
return err
|
||||
}
|
||||
continue Loop
|
||||
}
|
||||
}
|
||||
// Not saving sub-element but still have to skip over it.
|
||||
if err := p.Skip(); err != nil {
|
||||
return err
|
||||
if !consumed {
|
||||
if err := p.Skip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case EndElement:
|
||||
|
@ -503,10 +439,10 @@ func copyValue(dst reflect.Value, src []byte) (err error) {
|
|||
return err == nil
|
||||
}
|
||||
|
||||
// Save accumulated data and comments
|
||||
// Save accumulated data.
|
||||
switch t := dst; t.Kind() {
|
||||
case reflect.Invalid:
|
||||
// Probably a comment, handled below
|
||||
// Probably a comment.
|
||||
default:
|
||||
return errors.New("cannot happen: unknown type " + t.Type().String())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
|
@ -538,70 +474,66 @@ func copyValue(dst reflect.Value, src []byte) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
type pathInfo struct {
|
||||
fieldIdx []int
|
||||
complete bool
|
||||
}
|
||||
// unmarshalPath walks down an XML structure looking for wanted
|
||||
// paths, and calls unmarshal on them.
|
||||
// The consumed result tells whether XML elements have been consumed
|
||||
// from the Parser until start's matching end element, or if it's
|
||||
// still untouched because start is uninteresting for sv's fields.
|
||||
func (p *Parser) unmarshalPath(tinfo *typeInfo, sv reflect.Value, parents []string, start *StartElement) (consumed bool, err error) {
|
||||
recurse := false
|
||||
Loop:
|
||||
for i := range tinfo.fields {
|
||||
finfo := &tinfo.fields[i]
|
||||
if finfo.flags&fElement == 0 || len(finfo.parents) < len(parents) {
|
||||
continue
|
||||
}
|
||||
for j := range parents {
|
||||
if parents[j] != finfo.parents[j] {
|
||||
continue Loop
|
||||
}
|
||||
}
|
||||
if len(finfo.parents) == len(parents) && finfo.name == start.Name.Local {
|
||||
// It's a perfect match, unmarshal the field.
|
||||
return true, p.unmarshal(sv.FieldByIndex(finfo.idx), start)
|
||||
}
|
||||
if len(finfo.parents) > len(parents) && finfo.parents[len(parents)] == start.Name.Local {
|
||||
// It's a prefix for the field. Break and recurse
|
||||
// since it's not ok for one field path to be itself
|
||||
// the prefix for another field path.
|
||||
recurse = true
|
||||
|
||||
// addFieldPath takes an element path such as "a>b>c" and fills the
|
||||
// paths map with all paths leading to it ("a", "a>b", and "a>b>c").
|
||||
// It is okay for paths to share a common, shorter prefix but not ok
|
||||
// for one path to itself be a prefix of another.
|
||||
func addFieldPath(sv reflect.Value, paths map[string]pathInfo, path string, fieldIdx []int) error {
|
||||
if info, found := paths[path]; found {
|
||||
return tagError(sv, info.fieldIdx, fieldIdx)
|
||||
}
|
||||
paths[path] = pathInfo{fieldIdx, true}
|
||||
for {
|
||||
i := strings.LastIndex(path, ">")
|
||||
if i < 0 {
|
||||
// We can reuse the same slice as long as we
|
||||
// don't try to append to it.
|
||||
parents = finfo.parents[:len(parents)+1]
|
||||
break
|
||||
}
|
||||
path = path[:i]
|
||||
if info, found := paths[path]; found {
|
||||
if info.complete {
|
||||
return tagError(sv, info.fieldIdx, fieldIdx)
|
||||
}
|
||||
} else {
|
||||
paths[path] = pathInfo{fieldIdx, false}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func tagError(sv reflect.Value, idx1 []int, idx2 []int) error {
|
||||
t := sv.Type()
|
||||
f1 := t.FieldByIndex(idx1)
|
||||
f2 := t.FieldByIndex(idx2)
|
||||
return &TagPathError{t, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
|
||||
}
|
||||
|
||||
// unmarshalPaths walks down an XML structure looking for
|
||||
// wanted paths, and calls unmarshal on them.
|
||||
func (p *Parser) unmarshalPaths(sv reflect.Value, paths map[string]pathInfo, path string, start *StartElement) error {
|
||||
if info, _ := paths[path]; info.complete {
|
||||
return p.unmarshal(sv.FieldByIndex(info.fieldIdx), start)
|
||||
if !recurse {
|
||||
// We have no business with this element.
|
||||
return false, nil
|
||||
}
|
||||
// The element is not a perfect match for any field, but one
|
||||
// or more fields have the path to this element as a parent
|
||||
// prefix. Recurse and attempt to match these.
|
||||
for {
|
||||
tok, err := p.Token()
|
||||
var tok Token
|
||||
tok, err = p.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
return true, err
|
||||
}
|
||||
switch t := tok.(type) {
|
||||
case StartElement:
|
||||
k := path + ">" + fieldName(t.Name.Local)
|
||||
if _, found := paths[k]; found {
|
||||
if err := p.unmarshalPaths(sv, paths, k, &t); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
consumed2, err := p.unmarshalPath(tinfo, sv, parents, &t)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if err := p.Skip(); err != nil {
|
||||
return err
|
||||
if !consumed2 {
|
||||
if err := p.Skip(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
case EndElement:
|
||||
return nil
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
|
|
|
@ -6,6 +6,7 @@ package xml
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -13,7 +14,7 @@ import (
|
|||
|
||||
func TestUnmarshalFeed(t *testing.T) {
|
||||
var f Feed
|
||||
if err := Unmarshal(StringReader(atomFeedString), &f); err != nil {
|
||||
if err := Unmarshal(strings.NewReader(atomFeedString), &f); err != nil {
|
||||
t.Fatalf("Unmarshal: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(f, atomFeed) {
|
||||
|
@ -24,8 +25,8 @@ func TestUnmarshalFeed(t *testing.T) {
|
|||
// hget http://codereview.appspot.com/rss/mine/rsc
|
||||
const atomFeedString = `
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us"><title>Code Review - My issues</title><link href="http://codereview.appspot.com/" rel="alternate"></link><li-nk href="http://codereview.appspot.com/rss/mine/rsc" rel="self"></li-nk><id>http://codereview.appspot.com/</id><updated>2009-10-04T01:35:58+00:00</updated><author><name>rietveld<></name></author><entry><title>rietveld: an attempt at pubsubhubbub
|
||||
</title><link hre-f="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html">
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us"><title>Code Review - My issues</title><link href="http://codereview.appspot.com/" rel="alternate"></link><link href="http://codereview.appspot.com/rss/mine/rsc" rel="self"></link><id>http://codereview.appspot.com/</id><updated>2009-10-04T01:35:58+00:00</updated><author><name>rietveld<></name></author><entry><title>rietveld: an attempt at pubsubhubbub
|
||||
</title><link href="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html">
|
||||
An attempt at adding pubsubhubbub support to Rietveld.
|
||||
http://code.google.com/p/pubsubhubbub
|
||||
http://code.google.com/p/rietveld/issues/detail?id=155
|
||||
|
@ -78,39 +79,39 @@ not being used from outside intra_region_diff.py.
|
|||
</summary></entry></feed> `
|
||||
|
||||
type Feed struct {
|
||||
XMLName Name `xml:"http://www.w3.org/2005/Atom feed"`
|
||||
Title string
|
||||
Id string
|
||||
Link []Link
|
||||
Updated Time
|
||||
Author Person
|
||||
Entry []Entry
|
||||
XMLName Name `xml:"http://www.w3.org/2005/Atom feed"`
|
||||
Title string `xml:"title"`
|
||||
Id string `xml:"id"`
|
||||
Link []Link `xml:"link"`
|
||||
Updated Time `xml:"updated"`
|
||||
Author Person `xml:"author"`
|
||||
Entry []Entry `xml:"entry"`
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
Title string
|
||||
Id string
|
||||
Link []Link
|
||||
Updated Time
|
||||
Author Person
|
||||
Summary Text
|
||||
Title string `xml:"title"`
|
||||
Id string `xml:"id"`
|
||||
Link []Link `xml:"link"`
|
||||
Updated Time `xml:"updated"`
|
||||
Author Person `xml:"author"`
|
||||
Summary Text `xml:"summary"`
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
Rel string `xml:"attr"`
|
||||
Href string `xml:"attr"`
|
||||
Rel string `xml:"rel,attr"`
|
||||
Href string `xml:"href,attr"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
URI string
|
||||
Email string
|
||||
InnerXML string `xml:"innerxml"`
|
||||
Name string `xml:"name"`
|
||||
URI string `xml:"uri"`
|
||||
Email string `xml:"email"`
|
||||
InnerXML string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type Text struct {
|
||||
Type string `xml:"attr"`
|
||||
Body string `xml:"chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
Body string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type Time string
|
||||
|
@ -213,44 +214,26 @@ not being used from outside intra_region_diff.py.
|
|||
},
|
||||
}
|
||||
|
||||
type FieldNameTest struct {
|
||||
in, out string
|
||||
}
|
||||
|
||||
var FieldNameTests = []FieldNameTest{
|
||||
{"Profile-Image", "profileimage"},
|
||||
{"_score", "score"},
|
||||
}
|
||||
|
||||
func TestFieldName(t *testing.T) {
|
||||
for _, tt := range FieldNameTests {
|
||||
a := fieldName(tt.in)
|
||||
if a != tt.out {
|
||||
t.Fatalf("have %#v\nwant %#v\n\n", a, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pathTestString = `
|
||||
<result>
|
||||
<before>1</before>
|
||||
<items>
|
||||
<item1>
|
||||
<value>A</value>
|
||||
</item1>
|
||||
<item2>
|
||||
<value>B</value>
|
||||
</item2>
|
||||
<Result>
|
||||
<Before>1</Before>
|
||||
<Items>
|
||||
<Item1>
|
||||
<Value>A</Value>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<Value>B</Value>
|
||||
</Item2>
|
||||
<Item1>
|
||||
<Value>C</Value>
|
||||
<Value>D</Value>
|
||||
</Item1>
|
||||
<_>
|
||||
<value>E</value>
|
||||
<Value>E</Value>
|
||||
</_>
|
||||
</items>
|
||||
<after>2</after>
|
||||
</result>
|
||||
</Items>
|
||||
<After>2</After>
|
||||
</Result>
|
||||
`
|
||||
|
||||
type PathTestItem struct {
|
||||
|
@ -258,18 +241,18 @@ type PathTestItem struct {
|
|||
}
|
||||
|
||||
type PathTestA struct {
|
||||
Items []PathTestItem `xml:">item1"`
|
||||
Items []PathTestItem `xml:">Item1"`
|
||||
Before, After string
|
||||
}
|
||||
|
||||
type PathTestB struct {
|
||||
Other []PathTestItem `xml:"items>Item1"`
|
||||
Other []PathTestItem `xml:"Items>Item1"`
|
||||
Before, After string
|
||||
}
|
||||
|
||||
type PathTestC struct {
|
||||
Values1 []string `xml:"items>item1>value"`
|
||||
Values2 []string `xml:"items>item2>value"`
|
||||
Values1 []string `xml:"Items>Item1>Value"`
|
||||
Values2 []string `xml:"Items>Item2>Value"`
|
||||
Before, After string
|
||||
}
|
||||
|
||||
|
@ -278,12 +261,12 @@ type PathTestSet struct {
|
|||
}
|
||||
|
||||
type PathTestD struct {
|
||||
Other PathTestSet `xml:"items>"`
|
||||
Other PathTestSet `xml:"Items"`
|
||||
Before, After string
|
||||
}
|
||||
|
||||
type PathTestE struct {
|
||||
Underline string `xml:"items>_>value"`
|
||||
Underline string `xml:"Items>_>Value"`
|
||||
Before, After string
|
||||
}
|
||||
|
||||
|
@ -298,7 +281,7 @@ var pathTests = []interface{}{
|
|||
func TestUnmarshalPaths(t *testing.T) {
|
||||
for _, pt := range pathTests {
|
||||
v := reflect.New(reflect.TypeOf(pt).Elem()).Interface()
|
||||
if err := Unmarshal(StringReader(pathTestString), v); err != nil {
|
||||
if err := Unmarshal(strings.NewReader(pathTestString), v); err != nil {
|
||||
t.Fatalf("Unmarshal: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(v, pt) {
|
||||
|
@ -310,7 +293,7 @@ func TestUnmarshalPaths(t *testing.T) {
|
|||
type BadPathTestA struct {
|
||||
First string `xml:"items>item1"`
|
||||
Other string `xml:"items>item2"`
|
||||
Second string `xml:"items>"`
|
||||
Second string `xml:"items"`
|
||||
}
|
||||
|
||||
type BadPathTestB struct {
|
||||
|
@ -319,81 +302,55 @@ type BadPathTestB struct {
|
|||
Second string `xml:"items>item1>value"`
|
||||
}
|
||||
|
||||
type BadPathTestC struct {
|
||||
First string
|
||||
Second string `xml:"First"`
|
||||
}
|
||||
|
||||
type BadPathTestD struct {
|
||||
BadPathEmbeddedA
|
||||
BadPathEmbeddedB
|
||||
}
|
||||
|
||||
type BadPathEmbeddedA struct {
|
||||
First string
|
||||
}
|
||||
|
||||
type BadPathEmbeddedB struct {
|
||||
Second string `xml:"First"`
|
||||
}
|
||||
|
||||
var badPathTests = []struct {
|
||||
v, e interface{}
|
||||
}{
|
||||
{&BadPathTestA{}, &TagPathError{reflect.TypeOf(BadPathTestA{}), "First", "items>item1", "Second", "items>"}},
|
||||
{&BadPathTestA{}, &TagPathError{reflect.TypeOf(BadPathTestA{}), "First", "items>item1", "Second", "items"}},
|
||||
{&BadPathTestB{}, &TagPathError{reflect.TypeOf(BadPathTestB{}), "First", "items>item1", "Second", "items>item1>value"}},
|
||||
{&BadPathTestC{}, &TagPathError{reflect.TypeOf(BadPathTestC{}), "First", "", "Second", "First"}},
|
||||
{&BadPathTestD{}, &TagPathError{reflect.TypeOf(BadPathTestD{}), "First", "", "Second", "First"}},
|
||||
}
|
||||
|
||||
func TestUnmarshalBadPaths(t *testing.T) {
|
||||
for _, tt := range badPathTests {
|
||||
err := Unmarshal(StringReader(pathTestString), tt.v)
|
||||
err := Unmarshal(strings.NewReader(pathTestString), tt.v)
|
||||
if !reflect.DeepEqual(err, tt.e) {
|
||||
t.Fatalf("Unmarshal with %#v didn't fail properly: %#v", tt.v, err)
|
||||
t.Fatalf("Unmarshal with %#v didn't fail properly:\nhave %#v,\nwant %#v", tt.v, err, tt.e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalAttrs(t *testing.T) {
|
||||
var f AttrTest
|
||||
if err := Unmarshal(StringReader(attrString), &f); err != nil {
|
||||
t.Fatalf("Unmarshal: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(f, attrStruct) {
|
||||
t.Fatalf("have %#v\nwant %#v", f, attrStruct)
|
||||
}
|
||||
}
|
||||
|
||||
type AttrTest struct {
|
||||
Test1 Test1
|
||||
Test2 Test2
|
||||
}
|
||||
|
||||
type Test1 struct {
|
||||
Int int `xml:"attr"`
|
||||
Float float64 `xml:"attr"`
|
||||
Uint8 uint8 `xml:"attr"`
|
||||
}
|
||||
|
||||
type Test2 struct {
|
||||
Bool bool `xml:"attr"`
|
||||
}
|
||||
|
||||
const attrString = `
|
||||
<?xml version="1.0" charset="utf-8"?>
|
||||
<attrtest>
|
||||
<test1 int="8" float="23.5" uint8="255"/>
|
||||
<test2 bool="true"/>
|
||||
</attrtest>
|
||||
`
|
||||
|
||||
var attrStruct = AttrTest{
|
||||
Test1: Test1{
|
||||
Int: 8,
|
||||
Float: 23.5,
|
||||
Uint8: 255,
|
||||
},
|
||||
Test2: Test2{
|
||||
Bool: true,
|
||||
},
|
||||
}
|
||||
|
||||
// test data for TestUnmarshalWithoutNameType
|
||||
|
||||
const OK = "OK"
|
||||
const withoutNameTypeData = `
|
||||
<?xml version="1.0" charset="utf-8"?>
|
||||
<Test3 attr="OK" />`
|
||||
<Test3 Attr="OK" />`
|
||||
|
||||
type TestThree struct {
|
||||
XMLName bool `xml:"Test3"` // XMLName field without an xml.Name type
|
||||
Attr string `xml:"attr"`
|
||||
XMLName Name `xml:"Test3"`
|
||||
Attr string `xml:",attr"`
|
||||
}
|
||||
|
||||
func TestUnmarshalWithoutNameType(t *testing.T) {
|
||||
var x TestThree
|
||||
if err := Unmarshal(StringReader(withoutNameTypeData), &x); err != nil {
|
||||
if err := Unmarshal(strings.NewReader(withoutNameTypeData), &x); err != nil {
|
||||
t.Fatalf("Unmarshal: %s", err)
|
||||
}
|
||||
if x.Attr != OK {
|
||||
|
|
321
libgo/go/encoding/xml/typeinfo.go
Normal file
321
libgo/go/encoding/xml/typeinfo.go
Normal file
|
@ -0,0 +1,321 @@
|
|||
// 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 xml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// typeInfo holds details for the xml representation of a type.
|
||||
type typeInfo struct {
|
||||
xmlname *fieldInfo
|
||||
fields []fieldInfo
|
||||
}
|
||||
|
||||
// fieldInfo holds details for the xml representation of a single field.
|
||||
type fieldInfo struct {
|
||||
idx []int
|
||||
name string
|
||||
xmlns string
|
||||
flags fieldFlags
|
||||
parents []string
|
||||
}
|
||||
|
||||
type fieldFlags int
|
||||
|
||||
const (
|
||||
fElement fieldFlags = 1 << iota
|
||||
fAttr
|
||||
fCharData
|
||||
fInnerXml
|
||||
fComment
|
||||
fAny
|
||||
|
||||
// TODO:
|
||||
//fIgnore
|
||||
//fOmitEmpty
|
||||
|
||||
fMode = fElement | fAttr | fCharData | fInnerXml | fComment | fAny
|
||||
)
|
||||
|
||||
var tinfoMap = make(map[reflect.Type]*typeInfo)
|
||||
var tinfoLock sync.RWMutex
|
||||
|
||||
// getTypeInfo returns the typeInfo structure with details necessary
|
||||
// for marshalling and unmarshalling typ.
|
||||
func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
|
||||
tinfoLock.RLock()
|
||||
tinfo, ok := tinfoMap[typ]
|
||||
tinfoLock.RUnlock()
|
||||
if ok {
|
||||
return tinfo, nil
|
||||
}
|
||||
tinfo = &typeInfo{}
|
||||
if typ.Kind() == reflect.Struct {
|
||||
n := typ.NumField()
|
||||
for i := 0; i < n; i++ {
|
||||
f := typ.Field(i)
|
||||
if f.PkgPath != "" {
|
||||
continue // Private field
|
||||
}
|
||||
|
||||
// For embedded structs, embed its fields.
|
||||
if f.Anonymous {
|
||||
if f.Type.Kind() != reflect.Struct {
|
||||
continue
|
||||
}
|
||||
inner, err := getTypeInfo(f.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, finfo := range inner.fields {
|
||||
finfo.idx = append([]int{i}, finfo.idx...)
|
||||
if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
finfo, err := structFieldInfo(typ, &f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.Name == "XMLName" {
|
||||
tinfo.xmlname = finfo
|
||||
continue
|
||||
}
|
||||
|
||||
// Add the field if it doesn't conflict with other fields.
|
||||
if err := addFieldInfo(typ, tinfo, finfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
tinfoLock.Lock()
|
||||
tinfoMap[typ] = tinfo
|
||||
tinfoLock.Unlock()
|
||||
return tinfo, nil
|
||||
}
|
||||
|
||||
// structFieldInfo builds and returns a fieldInfo for f.
|
||||
func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) {
|
||||
finfo := &fieldInfo{idx: f.Index}
|
||||
|
||||
// Split the tag from the xml namespace if necessary.
|
||||
tag := f.Tag.Get("xml")
|
||||
if i := strings.Index(tag, " "); i >= 0 {
|
||||
finfo.xmlns, tag = tag[:i], tag[i+1:]
|
||||
}
|
||||
|
||||
// Parse flags.
|
||||
tokens := strings.Split(tag, ",")
|
||||
if len(tokens) == 1 {
|
||||
finfo.flags = fElement
|
||||
} else {
|
||||
tag = tokens[0]
|
||||
for _, flag := range tokens[1:] {
|
||||
switch flag {
|
||||
case "attr":
|
||||
finfo.flags |= fAttr
|
||||
case "chardata":
|
||||
finfo.flags |= fCharData
|
||||
case "innerxml":
|
||||
finfo.flags |= fInnerXml
|
||||
case "comment":
|
||||
finfo.flags |= fComment
|
||||
case "any":
|
||||
finfo.flags |= fAny
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the flags used.
|
||||
switch mode := finfo.flags & fMode; mode {
|
||||
case 0:
|
||||
finfo.flags |= fElement
|
||||
case fAttr, fCharData, fInnerXml, fComment, fAny:
|
||||
if f.Name != "XMLName" && (tag == "" || mode == fAttr) {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
// This will also catch multiple modes in a single field.
|
||||
return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q",
|
||||
f.Name, typ, f.Tag.Get("xml"))
|
||||
}
|
||||
}
|
||||
|
||||
// Use of xmlns without a name is not allowed.
|
||||
if finfo.xmlns != "" && tag == "" {
|
||||
return nil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q",
|
||||
f.Name, typ, f.Tag.Get("xml"))
|
||||
}
|
||||
|
||||
if f.Name == "XMLName" {
|
||||
// The XMLName field records the XML element name. Don't
|
||||
// process it as usual because its name should default to
|
||||
// empty rather than to the field name.
|
||||
finfo.name = tag
|
||||
return finfo, nil
|
||||
}
|
||||
|
||||
if tag == "" {
|
||||
// If the name part of the tag is completely empty, get
|
||||
// default from XMLName of underlying struct if feasible,
|
||||
// or field name otherwise.
|
||||
if xmlname := lookupXMLName(f.Type); xmlname != nil {
|
||||
finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name
|
||||
} else {
|
||||
finfo.name = f.Name
|
||||
}
|
||||
return finfo, nil
|
||||
}
|
||||
|
||||
// Prepare field name and parents.
|
||||
tokens = strings.Split(tag, ">")
|
||||
if tokens[0] == "" {
|
||||
tokens[0] = f.Name
|
||||
}
|
||||
if tokens[len(tokens)-1] == "" {
|
||||
return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ)
|
||||
}
|
||||
finfo.name = tokens[len(tokens)-1]
|
||||
if len(tokens) > 1 {
|
||||
finfo.parents = tokens[:len(tokens)-1]
|
||||
}
|
||||
|
||||
// If the field type has an XMLName field, the names must match
|
||||
// so that the behavior of both marshalling and unmarshalling
|
||||
// is straighforward and unambiguous.
|
||||
if finfo.flags&fElement != 0 {
|
||||
ftyp := f.Type
|
||||
xmlname := lookupXMLName(ftyp)
|
||||
if xmlname != nil && xmlname.name != finfo.name {
|
||||
return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName",
|
||||
finfo.name, typ, f.Name, xmlname.name, ftyp)
|
||||
}
|
||||
}
|
||||
return finfo, nil
|
||||
}
|
||||
|
||||
// lookupXMLName returns the fieldInfo for typ's XMLName field
|
||||
// in case it exists and has a valid xml field tag, otherwise
|
||||
// it returns nil.
|
||||
func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) {
|
||||
for typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
for i, n := 0, typ.NumField(); i < n; i++ {
|
||||
f := typ.Field(i)
|
||||
if f.Name != "XMLName" {
|
||||
continue
|
||||
}
|
||||
finfo, err := structFieldInfo(typ, &f)
|
||||
if finfo.name != "" && err == nil {
|
||||
return finfo
|
||||
}
|
||||
// Also consider errors as a non-existent field tag
|
||||
// and let getTypeInfo itself report the error.
|
||||
break
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a <= b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// addFieldInfo adds finfo to tinfo.fields if there are no
|
||||
// conflicts, or if conflicts arise from previous fields that were
|
||||
// obtained from deeper embedded structures than finfo. In the latter
|
||||
// case, the conflicting entries are dropped.
|
||||
// A conflict occurs when the path (parent + name) to a field is
|
||||
// itself a prefix of another path, or when two paths match exactly.
|
||||
// It is okay for field paths to share a common, shorter prefix.
|
||||
func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error {
|
||||
var conflicts []int
|
||||
Loop:
|
||||
// First, figure all conflicts. Most working code will have none.
|
||||
for i := range tinfo.fields {
|
||||
oldf := &tinfo.fields[i]
|
||||
if oldf.flags&fMode != newf.flags&fMode {
|
||||
continue
|
||||
}
|
||||
minl := min(len(newf.parents), len(oldf.parents))
|
||||
for p := 0; p < minl; p++ {
|
||||
if oldf.parents[p] != newf.parents[p] {
|
||||
continue Loop
|
||||
}
|
||||
}
|
||||
if len(oldf.parents) > len(newf.parents) {
|
||||
if oldf.parents[len(newf.parents)] == newf.name {
|
||||
conflicts = append(conflicts, i)
|
||||
}
|
||||
} else if len(oldf.parents) < len(newf.parents) {
|
||||
if newf.parents[len(oldf.parents)] == oldf.name {
|
||||
conflicts = append(conflicts, i)
|
||||
}
|
||||
} else {
|
||||
if newf.name == oldf.name {
|
||||
conflicts = append(conflicts, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Without conflicts, add the new field and return.
|
||||
if conflicts == nil {
|
||||
tinfo.fields = append(tinfo.fields, *newf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If any conflict is shallower, ignore the new field.
|
||||
// This matches the Go field resolution on embedding.
|
||||
for _, i := range conflicts {
|
||||
if len(tinfo.fields[i].idx) < len(newf.idx) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, if any of them is at the same depth level, it's an error.
|
||||
for _, i := range conflicts {
|
||||
oldf := &tinfo.fields[i]
|
||||
if len(oldf.idx) == len(newf.idx) {
|
||||
f1 := typ.FieldByIndex(oldf.idx)
|
||||
f2 := typ.FieldByIndex(newf.idx)
|
||||
return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, the new field is shallower, and thus takes precedence,
|
||||
// so drop the conflicting fields from tinfo and append the new one.
|
||||
for c := len(conflicts) - 1; c >= 0; c-- {
|
||||
i := conflicts[c]
|
||||
copy(tinfo.fields[i:], tinfo.fields[i+1:])
|
||||
tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
|
||||
}
|
||||
tinfo.fields = append(tinfo.fields, *newf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// A TagPathError represents an error in the unmarshalling process
|
||||
// caused by the use of field tags with conflicting paths.
|
||||
type TagPathError struct {
|
||||
Struct reflect.Type
|
||||
Field1, Tag1 string
|
||||
Field2, Tag2 string
|
||||
}
|
||||
|
||||
func (e *TagPathError) Error() string {
|
||||
return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2)
|
||||
}
|
|
@ -154,36 +154,8 @@ var xmlInput = []string{
|
|||
"<t>cdata]]></t>",
|
||||
}
|
||||
|
||||
type stringReader struct {
|
||||
s string
|
||||
off int
|
||||
}
|
||||
|
||||
func (r *stringReader) Read(b []byte) (n int, err error) {
|
||||
if r.off >= len(r.s) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
for r.off < len(r.s) && n < len(b) {
|
||||
b[n] = r.s[r.off]
|
||||
n++
|
||||
r.off++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *stringReader) ReadByte() (b byte, err error) {
|
||||
if r.off >= len(r.s) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
b = r.s[r.off]
|
||||
r.off++
|
||||
return
|
||||
}
|
||||
|
||||
func StringReader(s string) io.Reader { return &stringReader{s, 0} }
|
||||
|
||||
func TestRawToken(t *testing.T) {
|
||||
p := NewParser(StringReader(testInput))
|
||||
p := NewParser(strings.NewReader(testInput))
|
||||
testRawToken(t, p, rawTokens)
|
||||
}
|
||||
|
||||
|
@ -207,7 +179,7 @@ func (d *downCaser) Read(p []byte) (int, error) {
|
|||
|
||||
func TestRawTokenAltEncoding(t *testing.T) {
|
||||
sawEncoding := ""
|
||||
p := NewParser(StringReader(testInputAltEncoding))
|
||||
p := NewParser(strings.NewReader(testInputAltEncoding))
|
||||
p.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
|
||||
sawEncoding = charset
|
||||
if charset != "x-testing-uppercase" {
|
||||
|
@ -219,7 +191,7 @@ func TestRawTokenAltEncoding(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRawTokenAltEncodingNoConverter(t *testing.T) {
|
||||
p := NewParser(StringReader(testInputAltEncoding))
|
||||
p := NewParser(strings.NewReader(testInputAltEncoding))
|
||||
token, err := p.RawToken()
|
||||
if token == nil {
|
||||
t.Fatalf("expected a token on first RawToken call")
|
||||
|
@ -286,7 +258,7 @@ var nestedDirectivesTokens = []Token{
|
|||
}
|
||||
|
||||
func TestNestedDirectives(t *testing.T) {
|
||||
p := NewParser(StringReader(nestedDirectivesInput))
|
||||
p := NewParser(strings.NewReader(nestedDirectivesInput))
|
||||
|
||||
for i, want := range nestedDirectivesTokens {
|
||||
have, err := p.Token()
|
||||
|
@ -300,7 +272,7 @@ func TestNestedDirectives(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestToken(t *testing.T) {
|
||||
p := NewParser(StringReader(testInput))
|
||||
p := NewParser(strings.NewReader(testInput))
|
||||
|
||||
for i, want := range cookedTokens {
|
||||
have, err := p.Token()
|
||||
|
@ -315,7 +287,7 @@ func TestToken(t *testing.T) {
|
|||
|
||||
func TestSyntax(t *testing.T) {
|
||||
for i := range xmlInput {
|
||||
p := NewParser(StringReader(xmlInput[i]))
|
||||
p := NewParser(strings.NewReader(xmlInput[i]))
|
||||
var err error
|
||||
for _, err = p.Token(); err == nil; _, err = p.Token() {
|
||||
}
|
||||
|
@ -372,26 +344,26 @@ var all = allScalars{
|
|||
var sixteen = "16"
|
||||
|
||||
const testScalarsInput = `<allscalars>
|
||||
<true1>true</true1>
|
||||
<true2>1</true2>
|
||||
<false1>false</false1>
|
||||
<false2>0</false2>
|
||||
<int>1</int>
|
||||
<int8>-2</int8>
|
||||
<int16>3</int16>
|
||||
<int32>-4</int32>
|
||||
<int64>5</int64>
|
||||
<uint>6</uint>
|
||||
<uint8>7</uint8>
|
||||
<uint16>8</uint16>
|
||||
<uint32>9</uint32>
|
||||
<uint64>10</uint64>
|
||||
<uintptr>11</uintptr>
|
||||
<float>12.0</float>
|
||||
<float32>13.0</float32>
|
||||
<float64>14.0</float64>
|
||||
<string>15</string>
|
||||
<ptrstring>16</ptrstring>
|
||||
<True1>true</True1>
|
||||
<True2>1</True2>
|
||||
<False1>false</False1>
|
||||
<False2>0</False2>
|
||||
<Int>1</Int>
|
||||
<Int8>-2</Int8>
|
||||
<Int16>3</Int16>
|
||||
<Int32>-4</Int32>
|
||||
<Int64>5</Int64>
|
||||
<Uint>6</Uint>
|
||||
<Uint8>7</Uint8>
|
||||
<Uint16>8</Uint16>
|
||||
<Uint32>9</Uint32>
|
||||
<Uint64>10</Uint64>
|
||||
<Uintptr>11</Uintptr>
|
||||
<Float>12.0</Float>
|
||||
<Float32>13.0</Float32>
|
||||
<Float64>14.0</Float64>
|
||||
<String>15</String>
|
||||
<PtrString>16</PtrString>
|
||||
</allscalars>`
|
||||
|
||||
func TestAllScalars(t *testing.T) {
|
||||
|
@ -412,7 +384,7 @@ type item struct {
|
|||
}
|
||||
|
||||
func TestIssue569(t *testing.T) {
|
||||
data := `<item><field_a>abcd</field_a></item>`
|
||||
data := `<item><Field_a>abcd</Field_a></item>`
|
||||
var i item
|
||||
buf := bytes.NewBufferString(data)
|
||||
err := Unmarshal(buf, &i)
|
||||
|
@ -424,7 +396,7 @@ func TestIssue569(t *testing.T) {
|
|||
|
||||
func TestUnquotedAttrs(t *testing.T) {
|
||||
data := "<tag attr=azAZ09:-_\t>"
|
||||
p := NewParser(StringReader(data))
|
||||
p := NewParser(strings.NewReader(data))
|
||||
p.Strict = false
|
||||
token, err := p.Token()
|
||||
if _, ok := err.(*SyntaxError); ok {
|
||||
|
@ -450,7 +422,7 @@ func TestValuelessAttrs(t *testing.T) {
|
|||
{"<input checked />", "input", "checked"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
p := NewParser(StringReader(test[0]))
|
||||
p := NewParser(strings.NewReader(test[0]))
|
||||
p.Strict = false
|
||||
token, err := p.Token()
|
||||
if _, ok := err.(*SyntaxError); ok {
|
||||
|
@ -500,7 +472,7 @@ func TestCopyTokenStartElement(t *testing.T) {
|
|||
|
||||
func TestSyntaxErrorLineNum(t *testing.T) {
|
||||
testInput := "<P>Foo<P>\n\n<P>Bar</>\n"
|
||||
p := NewParser(StringReader(testInput))
|
||||
p := NewParser(strings.NewReader(testInput))
|
||||
var err error
|
||||
for _, err = p.Token(); err == nil; _, err = p.Token() {
|
||||
}
|
||||
|
@ -515,7 +487,7 @@ func TestSyntaxErrorLineNum(t *testing.T) {
|
|||
|
||||
func TestTrailingRawToken(t *testing.T) {
|
||||
input := `<FOO></FOO> `
|
||||
p := NewParser(StringReader(input))
|
||||
p := NewParser(strings.NewReader(input))
|
||||
var err error
|
||||
for _, err = p.RawToken(); err == nil; _, err = p.RawToken() {
|
||||
}
|
||||
|
@ -526,7 +498,7 @@ func TestTrailingRawToken(t *testing.T) {
|
|||
|
||||
func TestTrailingToken(t *testing.T) {
|
||||
input := `<FOO></FOO> `
|
||||
p := NewParser(StringReader(input))
|
||||
p := NewParser(strings.NewReader(input))
|
||||
var err error
|
||||
for _, err = p.Token(); err == nil; _, err = p.Token() {
|
||||
}
|
||||
|
@ -537,7 +509,7 @@ func TestTrailingToken(t *testing.T) {
|
|||
|
||||
func TestEntityInsideCDATA(t *testing.T) {
|
||||
input := `<test><![CDATA[ &val=foo ]]></test>`
|
||||
p := NewParser(StringReader(input))
|
||||
p := NewParser(strings.NewReader(input))
|
||||
var err error
|
||||
for _, err = p.Token(); err == nil; _, err = p.Token() {
|
||||
}
|
||||
|
@ -569,7 +541,7 @@ var characterTests = []struct {
|
|||
func TestDisallowedCharacters(t *testing.T) {
|
||||
|
||||
for i, tt := range characterTests {
|
||||
p := NewParser(StringReader(tt.in))
|
||||
p := NewParser(strings.NewReader(tt.in))
|
||||
var err error
|
||||
|
||||
for err == nil {
|
||||
|
|
|
@ -8,7 +8,7 @@ import "unicode/utf8"
|
|||
|
||||
type input interface {
|
||||
skipASCII(p int) int
|
||||
skipNonStarter() int
|
||||
skipNonStarter(p int) int
|
||||
appendSlice(buf []byte, s, e int) []byte
|
||||
copySlice(buf []byte, s, e int)
|
||||
charinfo(p int) (uint16, int)
|
||||
|
@ -25,8 +25,7 @@ func (s inputString) skipASCII(p int) int {
|
|||
return p
|
||||
}
|
||||
|
||||
func (s inputString) skipNonStarter() int {
|
||||
p := 0
|
||||
func (s inputString) skipNonStarter(p int) int {
|
||||
for ; p < len(s) && !utf8.RuneStart(s[p]); p++ {
|
||||
}
|
||||
return p
|
||||
|
@ -71,8 +70,7 @@ func (s inputBytes) skipASCII(p int) int {
|
|||
return p
|
||||
}
|
||||
|
||||
func (s inputBytes) skipNonStarter() int {
|
||||
p := 0
|
||||
func (s inputBytes) skipNonStarter(p int) int {
|
||||
for ; p < len(s) && !utf8.RuneStart(s[p]); p++ {
|
||||
}
|
||||
return p
|
||||
|
|
|
@ -34,24 +34,28 @@ const (
|
|||
|
||||
// Bytes returns f(b). May return b if f(b) = b.
|
||||
func (f Form) Bytes(b []byte) []byte {
|
||||
n := f.QuickSpan(b)
|
||||
rb := reorderBuffer{}
|
||||
rb.init(f, b)
|
||||
n := quickSpan(&rb, 0)
|
||||
if n == len(b) {
|
||||
return b
|
||||
}
|
||||
out := make([]byte, n, len(b))
|
||||
copy(out, b[0:n])
|
||||
return f.Append(out, b[n:]...)
|
||||
return doAppend(&rb, out, n)
|
||||
}
|
||||
|
||||
// String returns f(s).
|
||||
func (f Form) String(s string) string {
|
||||
n := f.QuickSpanString(s)
|
||||
rb := reorderBuffer{}
|
||||
rb.initString(f, s)
|
||||
n := quickSpan(&rb, 0)
|
||||
if n == len(s) {
|
||||
return s
|
||||
}
|
||||
out := make([]byte, 0, len(s))
|
||||
out := make([]byte, n, len(s))
|
||||
copy(out, s[0:n])
|
||||
return string(f.AppendString(out, s[n:]))
|
||||
return string(doAppend(&rb, out, n))
|
||||
}
|
||||
|
||||
// IsNormal returns true if b == f(b).
|
||||
|
@ -122,23 +126,27 @@ func (f Form) IsNormalString(s string) bool {
|
|||
|
||||
// patchTail fixes a case where a rune may be incorrectly normalized
|
||||
// if it is followed by illegal continuation bytes. It returns the
|
||||
// patched buffer and the number of trailing continuation bytes that
|
||||
// have been dropped.
|
||||
func patchTail(rb *reorderBuffer, buf []byte) ([]byte, int) {
|
||||
// patched buffer and whether there were trailing continuation bytes.
|
||||
func patchTail(rb *reorderBuffer, buf []byte) ([]byte, bool) {
|
||||
info, p := lastRuneStart(&rb.f, buf)
|
||||
if p == -1 || info.size == 0 {
|
||||
return buf, 0
|
||||
return buf, false
|
||||
}
|
||||
end := p + int(info.size)
|
||||
extra := len(buf) - end
|
||||
if extra > 0 {
|
||||
// Potentially allocating memory. However, this only
|
||||
// happens with ill-formed UTF-8.
|
||||
x := make([]byte, 0)
|
||||
x = append(x, buf[len(buf)-extra:]...)
|
||||
buf = decomposeToLastBoundary(rb, buf[:end])
|
||||
if rb.f.composing {
|
||||
rb.compose()
|
||||
}
|
||||
return rb.flush(buf), extra
|
||||
buf = rb.flush(buf)
|
||||
return append(buf, x...), true
|
||||
}
|
||||
return buf, 0
|
||||
return buf, false
|
||||
}
|
||||
|
||||
func appendQuick(rb *reorderBuffer, dst []byte, i int) ([]byte, int) {
|
||||
|
@ -157,23 +165,23 @@ func (f Form) Append(out []byte, src ...byte) []byte {
|
|||
}
|
||||
rb := reorderBuffer{}
|
||||
rb.init(f, src)
|
||||
return doAppend(&rb, out)
|
||||
return doAppend(&rb, out, 0)
|
||||
}
|
||||
|
||||
func doAppend(rb *reorderBuffer, out []byte) []byte {
|
||||
func doAppend(rb *reorderBuffer, out []byte, p int) []byte {
|
||||
src, n := rb.src, rb.nsrc
|
||||
doMerge := len(out) > 0
|
||||
p := 0
|
||||
if p = src.skipNonStarter(); p > 0 {
|
||||
if q := src.skipNonStarter(p); q > p {
|
||||
// Move leading non-starters to destination.
|
||||
out = src.appendSlice(out, 0, p)
|
||||
buf, ndropped := patchTail(rb, out)
|
||||
if ndropped > 0 {
|
||||
out = src.appendSlice(buf, p-ndropped, p)
|
||||
out = src.appendSlice(out, p, q)
|
||||
buf, endsInError := patchTail(rb, out)
|
||||
if endsInError {
|
||||
out = buf
|
||||
doMerge = false // no need to merge, ends with illegal UTF-8
|
||||
} else {
|
||||
out = decomposeToLastBoundary(rb, buf) // force decomposition
|
||||
}
|
||||
p = q
|
||||
}
|
||||
fd := &rb.f
|
||||
if doMerge {
|
||||
|
@ -217,7 +225,7 @@ func (f Form) AppendString(out []byte, src string) []byte {
|
|||
}
|
||||
rb := reorderBuffer{}
|
||||
rb.initString(f, src)
|
||||
return doAppend(&rb, out)
|
||||
return doAppend(&rb, out, 0)
|
||||
}
|
||||
|
||||
// QuickSpan returns a boundary n such that b[0:n] == f(b[0:n]).
|
||||
|
@ -225,7 +233,8 @@ func (f Form) AppendString(out []byte, src string) []byte {
|
|||
func (f Form) QuickSpan(b []byte) int {
|
||||
rb := reorderBuffer{}
|
||||
rb.init(f, b)
|
||||
return quickSpan(&rb, 0)
|
||||
n := quickSpan(&rb, 0)
|
||||
return n
|
||||
}
|
||||
|
||||
func quickSpan(rb *reorderBuffer, i int) int {
|
||||
|
@ -301,7 +310,7 @@ func (f Form) FirstBoundary(b []byte) int {
|
|||
|
||||
func firstBoundary(rb *reorderBuffer) int {
|
||||
src, nsrc := rb.src, rb.nsrc
|
||||
i := src.skipNonStarter()
|
||||
i := src.skipNonStarter(0)
|
||||
if i >= nsrc {
|
||||
return -1
|
||||
}
|
||||
|
|
|
@ -253,7 +253,7 @@ var quickSpanNFDTests = []PositionTest{
|
|||
{"\u0316\u0300cd", 6, ""},
|
||||
{"\u043E\u0308b", 5, ""},
|
||||
// incorrectly ordered combining characters
|
||||
{"ab\u0300\u0316", 1, ""}, // TODO(mpvl): we could skip 'b' as well.
|
||||
{"ab\u0300\u0316", 1, ""}, // TODO: we could skip 'b' as well.
|
||||
{"ab\u0300\u0316cd", 1, ""},
|
||||
// Hangul
|
||||
{"같은", 0, ""},
|
||||
|
@ -465,6 +465,7 @@ var appendTests = []AppendTest{
|
|||
{"\u0300", "\xFC\x80\x80\x80\x80\x80\u0300", "\u0300\xFC\x80\x80\x80\x80\x80\u0300"},
|
||||
{"\xF8\x80\x80\x80\x80\u0300", "\u0300", "\xF8\x80\x80\x80\x80\u0300\u0300"},
|
||||
{"\xFC\x80\x80\x80\x80\x80\u0300", "\u0300", "\xFC\x80\x80\x80\x80\x80\u0300\u0300"},
|
||||
{"\xF8\x80\x80\x80", "\x80\u0300\u0300", "\xF8\x80\x80\x80\x80\u0300\u0300"},
|
||||
}
|
||||
|
||||
func appendF(f Form, out []byte, s string) []byte {
|
||||
|
@ -475,9 +476,23 @@ func appendStringF(f Form, out []byte, s string) []byte {
|
|||
return f.AppendString(out, s)
|
||||
}
|
||||
|
||||
func bytesF(f Form, out []byte, s string) []byte {
|
||||
buf := []byte{}
|
||||
buf = append(buf, out...)
|
||||
buf = append(buf, s...)
|
||||
return f.Bytes(buf)
|
||||
}
|
||||
|
||||
func stringF(f Form, out []byte, s string) []byte {
|
||||
outs := string(out) + s
|
||||
return []byte(f.String(outs))
|
||||
}
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
runAppendTests(t, "TestAppend", NFKC, appendF, appendTests)
|
||||
runAppendTests(t, "TestAppendString", NFKC, appendStringF, appendTests)
|
||||
runAppendTests(t, "TestBytes", NFKC, bytesF, appendTests)
|
||||
runAppendTests(t, "TestString", NFKC, stringF, appendTests)
|
||||
}
|
||||
|
||||
func doFormBenchmark(b *testing.B, f Form, s string) {
|
||||
|
|
|
@ -27,7 +27,7 @@ func (w *normWriter) Write(data []byte) (n int, err error) {
|
|||
}
|
||||
w.rb.src = inputBytes(data[:m])
|
||||
w.rb.nsrc = m
|
||||
w.buf = doAppend(&w.rb, w.buf)
|
||||
w.buf = doAppend(&w.rb, w.buf, 0)
|
||||
data = data[m:]
|
||||
n += m
|
||||
|
||||
|
@ -101,7 +101,7 @@ func (r *normReader) Read(p []byte) (int, error) {
|
|||
r.rb.src = inputBytes(r.inbuf[0:n])
|
||||
r.rb.nsrc, r.err = n, err
|
||||
if n > 0 {
|
||||
r.outbuf = doAppend(&r.rb, r.outbuf)
|
||||
r.outbuf = doAppend(&r.rb, r.outbuf, 0)
|
||||
}
|
||||
if err == io.EOF {
|
||||
r.lastBoundary = len(r.outbuf)
|
||||
|
|
18
libgo/go/exp/proxy/direct.go
Normal file
18
libgo/go/exp/proxy/direct.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
// 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 proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type direct struct{}
|
||||
|
||||
// Direct is a direct proxy: one that makes network connections directly.
|
||||
var Direct = direct{}
|
||||
|
||||
func (direct) Dial(network, addr string) (net.Conn, error) {
|
||||
return net.Dial(network, addr)
|
||||
}
|
140
libgo/go/exp/proxy/per_host.go
Normal file
140
libgo/go/exp/proxy/per_host.go
Normal file
|
@ -0,0 +1,140 @@
|
|||
// 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 proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A PerHost directs connections to a default Dailer unless the hostname
|
||||
// requested matches one of a number of exceptions.
|
||||
type PerHost struct {
|
||||
def, bypass Dialer
|
||||
|
||||
bypassNetworks []*net.IPNet
|
||||
bypassIPs []net.IP
|
||||
bypassZones []string
|
||||
bypassHosts []string
|
||||
}
|
||||
|
||||
// NewPerHost returns a PerHost Dialer that directs connections to either
|
||||
// defaultDialer or bypass, depending on whether the connection matches one of
|
||||
// the configured rules.
|
||||
func NewPerHost(defaultDialer, bypass Dialer) *PerHost {
|
||||
return &PerHost{
|
||||
def: defaultDialer,
|
||||
bypass: bypass,
|
||||
}
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net through either
|
||||
// defaultDialer or bypass.
|
||||
func (p *PerHost) Dial(network, addr string) (c net.Conn, err error) {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.dialerForRequest(host).Dial(network, addr)
|
||||
}
|
||||
|
||||
func (p *PerHost) dialerForRequest(host string) Dialer {
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
for _, net := range p.bypassNetworks {
|
||||
if net.Contains(ip) {
|
||||
return p.bypass
|
||||
}
|
||||
}
|
||||
for _, bypassIP := range p.bypassIPs {
|
||||
if bypassIP.Equal(ip) {
|
||||
return p.bypass
|
||||
}
|
||||
}
|
||||
return p.def
|
||||
}
|
||||
|
||||
for _, zone := range p.bypassZones {
|
||||
if strings.HasSuffix(host, zone) {
|
||||
return p.bypass
|
||||
}
|
||||
if host == zone[1:] {
|
||||
// For a zone "example.com", we match "example.com"
|
||||
// too.
|
||||
return p.bypass
|
||||
}
|
||||
}
|
||||
for _, bypassHost := range p.bypassHosts {
|
||||
if bypassHost == host {
|
||||
return p.bypass
|
||||
}
|
||||
}
|
||||
return p.def
|
||||
}
|
||||
|
||||
// AddFromString parses a string that contains comma-separated values
|
||||
// specifing hosts that should use the bypass proxy. Each value is either an
|
||||
// IP address, a CIDR range, a zone (*.example.com) or a hostname
|
||||
// (localhost). A best effort is made to parse the string and errors are
|
||||
// ignored.
|
||||
func (p *PerHost) AddFromString(s string) {
|
||||
hosts := strings.Split(s, ",")
|
||||
for _, host := range hosts {
|
||||
host = strings.TrimSpace(host)
|
||||
if len(host) == 0 {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(host, "/") {
|
||||
// We assume that it's a CIDR address like 127.0.0.0/8
|
||||
if _, net, err := net.ParseCIDR(host); err == nil {
|
||||
p.AddNetwork(net)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
p.AddIP(ip)
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(host, "*.") {
|
||||
p.AddZone(host[1:])
|
||||
continue
|
||||
}
|
||||
p.AddHost(host)
|
||||
}
|
||||
}
|
||||
|
||||
// AddIP specifies an IP address that will use the bypass proxy. Note that
|
||||
// this will only take effect if a literal IP address is dialed. A connection
|
||||
// to a named host will never match an IP.
|
||||
func (p *PerHost) AddIP(ip net.IP) {
|
||||
p.bypassIPs = append(p.bypassIPs, ip)
|
||||
}
|
||||
|
||||
// AddIP specifies an IP range that will use the bypass proxy. Note that this
|
||||
// will only take effect if a literal IP address is dialed. A connection to a
|
||||
// named host will never match.
|
||||
func (p *PerHost) AddNetwork(net *net.IPNet) {
|
||||
p.bypassNetworks = append(p.bypassNetworks, net)
|
||||
}
|
||||
|
||||
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
|
||||
// "example.com" matches "example.com" and all of its subdomains.
|
||||
func (p *PerHost) AddZone(zone string) {
|
||||
if strings.HasSuffix(zone, ".") {
|
||||
zone = zone[:len(zone)-1]
|
||||
}
|
||||
if !strings.HasPrefix(zone, ".") {
|
||||
zone = "." + zone
|
||||
}
|
||||
p.bypassZones = append(p.bypassZones, zone)
|
||||
}
|
||||
|
||||
// AddHost specifies a hostname that will use the bypass proxy.
|
||||
func (p *PerHost) AddHost(host string) {
|
||||
if strings.HasSuffix(host, ".") {
|
||||
host = host[:len(host)-1]
|
||||
}
|
||||
p.bypassHosts = append(p.bypassHosts, host)
|
||||
}
|
55
libgo/go/exp/proxy/per_host_test.go
Normal file
55
libgo/go/exp/proxy/per_host_test.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
// 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 proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type recordingProxy struct {
|
||||
addrs []string
|
||||
}
|
||||
|
||||
func (r *recordingProxy) Dial(network, addr string) (net.Conn, error) {
|
||||
r.addrs = append(r.addrs, addr)
|
||||
return nil, errors.New("recordingProxy")
|
||||
}
|
||||
|
||||
func TestPerHost(t *testing.T) {
|
||||
var def, bypass recordingProxy
|
||||
perHost := NewPerHost(&def, &bypass)
|
||||
perHost.AddFromString("localhost,*.zone,127.0.0.1,10.0.0.1/8,1000::/16")
|
||||
|
||||
expectedDef := []string{
|
||||
"example.com:123",
|
||||
"1.2.3.4:123",
|
||||
"[1001::]:123",
|
||||
}
|
||||
expectedBypass := []string{
|
||||
"localhost:123",
|
||||
"zone:123",
|
||||
"foo.zone:123",
|
||||
"127.0.0.1:123",
|
||||
"10.1.2.3:123",
|
||||
"[1000::]:123",
|
||||
}
|
||||
|
||||
for _, addr := range expectedDef {
|
||||
perHost.Dial("tcp", addr)
|
||||
}
|
||||
for _, addr := range expectedBypass {
|
||||
perHost.Dial("tcp", addr)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expectedDef, def.addrs) {
|
||||
t.Errorf("Hosts which went to the default proxy didn't match. Got %v, want %v", def.addrs, expectedDef)
|
||||
}
|
||||
if !reflect.DeepEqual(expectedBypass, bypass.addrs) {
|
||||
t.Errorf("Hosts which went to the bypass proxy didn't match. Got %v, want %v", bypass.addrs, expectedBypass)
|
||||
}
|
||||
}
|
98
libgo/go/exp/proxy/proxy.go
Normal file
98
libgo/go/exp/proxy/proxy.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
// 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 proxy provides support for a variety of protocols to proxy network
|
||||
// data.
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Dialer is a means to establish a connection.
|
||||
type Dialer interface {
|
||||
// Dial connects to the given address via the proxy.
|
||||
Dial(network, addr string) (c net.Conn, err error)
|
||||
}
|
||||
|
||||
// Auth contains authentication parameters that specific Dialers may require.
|
||||
type Auth struct {
|
||||
User, Password string
|
||||
}
|
||||
|
||||
// DefaultDialer returns the dialer specified by the proxy related variables in
|
||||
// the environment.
|
||||
func FromEnvironment() Dialer {
|
||||
allProxy := os.Getenv("all_proxy")
|
||||
if len(allProxy) == 0 {
|
||||
return Direct
|
||||
}
|
||||
|
||||
proxyURL, err := url.Parse(allProxy)
|
||||
if err != nil {
|
||||
return Direct
|
||||
}
|
||||
proxy, err := FromURL(proxyURL, Direct)
|
||||
if err != nil {
|
||||
return Direct
|
||||
}
|
||||
|
||||
noProxy := os.Getenv("no_proxy")
|
||||
if len(noProxy) == 0 {
|
||||
return proxy
|
||||
}
|
||||
|
||||
perHost := NewPerHost(proxy, Direct)
|
||||
perHost.AddFromString(noProxy)
|
||||
return perHost
|
||||
}
|
||||
|
||||
// proxySchemes is a map from URL schemes to a function that creates a Dialer
|
||||
// from a URL with such a scheme.
|
||||
var proxySchemes map[string]func(*url.URL, Dialer) (Dialer, error)
|
||||
|
||||
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
|
||||
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
|
||||
// by FromURL.
|
||||
func RegisterDialerType(scheme string, f func(*url.URL, Dialer) (Dialer, error)) {
|
||||
if proxySchemes == nil {
|
||||
proxySchemes = make(map[string]func(*url.URL, Dialer) (Dialer, error))
|
||||
}
|
||||
proxySchemes[scheme] = f
|
||||
}
|
||||
|
||||
// FromURL returns a Dialer given a URL specification and an underlying
|
||||
// Dialer for it to make network requests.
|
||||
func FromURL(u *url.URL, forward Dialer) (Dialer, error) {
|
||||
var auth *Auth
|
||||
if len(u.RawUserinfo) > 0 {
|
||||
auth = new(Auth)
|
||||
parts := strings.SplitN(u.RawUserinfo, ":", 1)
|
||||
if len(parts) == 1 {
|
||||
auth.User = parts[0]
|
||||
} else if len(parts) >= 2 {
|
||||
auth.User = parts[0]
|
||||
auth.Password = parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "socks5":
|
||||
return SOCKS5("tcp", u.Host, auth, forward)
|
||||
}
|
||||
|
||||
// If the scheme doesn't match any of the built-in schemes, see if it
|
||||
// was registered by another package.
|
||||
if proxySchemes != nil {
|
||||
if f, ok := proxySchemes[u.Scheme]; ok {
|
||||
return f(u, forward)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
|
||||
}
|
50
libgo/go/exp/proxy/proxy_test.go
Normal file
50
libgo/go/exp/proxy/proxy_test.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
// 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 proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testDialer struct {
|
||||
network, addr string
|
||||
}
|
||||
|
||||
func (t *testDialer) Dial(network, addr string) (net.Conn, error) {
|
||||
t.network = network
|
||||
t.addr = addr
|
||||
return nil, t
|
||||
}
|
||||
|
||||
func (t *testDialer) Error() string {
|
||||
return "testDialer " + t.network + " " + t.addr
|
||||
}
|
||||
|
||||
func TestFromURL(t *testing.T) {
|
||||
u, err := url.Parse("socks5://user:password@1.2.3.4:5678")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse URL: %s", err)
|
||||
}
|
||||
|
||||
tp := &testDialer{}
|
||||
proxy, err := FromURL(u, tp)
|
||||
if err != nil {
|
||||
t.Fatalf("FromURL failed: %s", err)
|
||||
}
|
||||
|
||||
conn, err := proxy.Dial("tcp", "example.com:80")
|
||||
if conn != nil {
|
||||
t.Error("Dial unexpected didn't return an error")
|
||||
}
|
||||
if tp, ok := err.(*testDialer); ok {
|
||||
if tp.network != "tcp" || tp.addr != "1.2.3.4:5678" {
|
||||
t.Errorf("Dialer connected to wrong host. Wanted 1.2.3.4:5678, got: %v", tp)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("Unexpected error from Dial: %s", err)
|
||||
}
|
||||
}
|
207
libgo/go/exp/proxy/socks5.go
Normal file
207
libgo/go/exp/proxy/socks5.go
Normal file
|
@ -0,0 +1,207 @@
|
|||
// 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 proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||||
// with an optional username and password. See RFC 1928.
|
||||
func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
|
||||
s := &socks5{
|
||||
network: network,
|
||||
addr: addr,
|
||||
forward: forward,
|
||||
}
|
||||
if auth != nil {
|
||||
s.user = auth.User
|
||||
s.password = auth.Password
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type socks5 struct {
|
||||
user, password string
|
||||
network, addr string
|
||||
forward Dialer
|
||||
}
|
||||
|
||||
const socks5Version = 5
|
||||
|
||||
const (
|
||||
socks5AuthNone = 0
|
||||
socks5AuthPassword = 2
|
||||
)
|
||||
|
||||
const socks5Connect = 1
|
||||
|
||||
const (
|
||||
socks5IP4 = 1
|
||||
socks5Domain = 3
|
||||
socks5IP6 = 4
|
||||
)
|
||||
|
||||
var socks5Errors = []string{
|
||||
"",
|
||||
"general failure",
|
||||
"connection forbidden",
|
||||
"network unreachable",
|
||||
"host unreachable",
|
||||
"connection refused",
|
||||
"TTL expired",
|
||||
"command not supported",
|
||||
"address type not supported",
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
|
||||
func (s *socks5) Dial(network, addr string) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp", "tcp6", "tcp4":
|
||||
break
|
||||
default:
|
||||
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
|
||||
}
|
||||
|
||||
conn, err := s.forward.Dial(s.network, s.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
closeConn := &conn
|
||||
defer func() {
|
||||
if closeConn != nil {
|
||||
(*closeConn).Close()
|
||||
}
|
||||
}()
|
||||
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return nil, errors.New("proxy: failed to parse port number: " + portStr)
|
||||
}
|
||||
if port < 1 || port > 0xffff {
|
||||
return nil, errors.New("proxy: port number out of range: " + portStr)
|
||||
}
|
||||
|
||||
// the size here is just an estimate
|
||||
buf := make([]byte, 0, 6+len(host))
|
||||
|
||||
buf = append(buf, socks5Version)
|
||||
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||
buf = append(buf, 2, /* num auth methods */ socks5AuthNone, socks5AuthPassword)
|
||||
} else {
|
||||
buf = append(buf, 1, /* num auth methods */ socks5AuthNone)
|
||||
}
|
||||
|
||||
if _, err = conn.Write(buf); err != nil {
|
||||
return nil, errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return nil, errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
if buf[0] != 5 {
|
||||
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||
}
|
||||
if buf[1] == 0xff {
|
||||
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||
}
|
||||
|
||||
if buf[1] == socks5AuthPassword {
|
||||
buf = buf[:0]
|
||||
buf = append(buf, socks5Version)
|
||||
buf = append(buf, uint8(len(s.user)))
|
||||
buf = append(buf, s.user...)
|
||||
buf = append(buf, uint8(len(s.password)))
|
||||
buf = append(buf, s.password...)
|
||||
|
||||
if _, err = conn.Write(buf); err != nil {
|
||||
return nil, errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return nil, errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if buf[1] != 0 {
|
||||
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||
}
|
||||
}
|
||||
|
||||
buf = buf[:0]
|
||||
buf = append(buf, socks5Version, socks5Connect, 0 /* reserved */ )
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if len(ip) == 4 {
|
||||
buf = append(buf, socks5IP4)
|
||||
} else {
|
||||
buf = append(buf, socks5IP6)
|
||||
}
|
||||
buf = append(buf, []byte(ip)...)
|
||||
} else {
|
||||
buf = append(buf, socks5Domain)
|
||||
buf = append(buf, byte(len(host)))
|
||||
buf = append(buf, host...)
|
||||
}
|
||||
buf = append(buf, byte(port>>8), byte(port))
|
||||
|
||||
if _, err = conn.Write(buf); err != nil {
|
||||
return nil, errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(conn, buf[:4]); err != nil {
|
||||
return nil, errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
failure := "unknown error"
|
||||
if int(buf[1]) < len(socks5Errors) {
|
||||
failure = socks5Errors[buf[1]]
|
||||
}
|
||||
|
||||
if len(failure) > 0 {
|
||||
return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||
}
|
||||
|
||||
bytesToDiscard := 0
|
||||
switch buf[3] {
|
||||
case socks5IP4:
|
||||
bytesToDiscard = 4
|
||||
case socks5IP6:
|
||||
bytesToDiscard = 16
|
||||
case socks5Domain:
|
||||
_, err := io.ReadFull(conn, buf[:1])
|
||||
if err != nil {
|
||||
return nil, errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
bytesToDiscard = int(buf[0])
|
||||
default:
|
||||
return nil, errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||
}
|
||||
|
||||
if cap(buf) < bytesToDiscard {
|
||||
buf = make([]byte, bytesToDiscard)
|
||||
} else {
|
||||
buf = buf[:bytesToDiscard]
|
||||
}
|
||||
if _, err = io.ReadFull(conn, buf); err != nil {
|
||||
return nil, errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
// Also need to discard the port number
|
||||
if _, err = io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return nil, errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
closeConn = nil
|
||||
return conn, nil
|
||||
}
|
|
@ -8,8 +8,11 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var someTime = time.Unix(123, 0)
|
||||
|
||||
type conversionTest struct {
|
||||
s, d interface{} // source and destination
|
||||
|
||||
|
@ -19,6 +22,7 @@ type conversionTest struct {
|
|||
wantstr string
|
||||
wantf32 float32
|
||||
wantf64 float64
|
||||
wanttime time.Time
|
||||
wantbool bool // used if d is of type *bool
|
||||
wanterr string
|
||||
}
|
||||
|
@ -35,12 +39,14 @@ var (
|
|||
scanbool bool
|
||||
scanf32 float32
|
||||
scanf64 float64
|
||||
scantime time.Time
|
||||
)
|
||||
|
||||
var conversionTests = []conversionTest{
|
||||
// Exact conversions (destination pointer type matches source type)
|
||||
{s: "foo", d: &scanstr, wantstr: "foo"},
|
||||
{s: 123, d: &scanint, wantint: 123},
|
||||
{s: someTime, d: &scantime, wanttime: someTime},
|
||||
|
||||
// To strings
|
||||
{s: []byte("byteslice"), d: &scanstr, wantstr: "byteslice"},
|
||||
|
@ -106,6 +112,10 @@ func float32Value(ptr interface{}) float32 {
|
|||
return *(ptr.(*float32))
|
||||
}
|
||||
|
||||
func timeValue(ptr interface{}) time.Time {
|
||||
return *(ptr.(*time.Time))
|
||||
}
|
||||
|
||||
func TestConversions(t *testing.T) {
|
||||
for n, ct := range conversionTests {
|
||||
err := convertAssign(ct.d, ct.s)
|
||||
|
@ -138,6 +148,9 @@ func TestConversions(t *testing.T) {
|
|||
if bp, boolTest := ct.d.(*bool); boolTest && *bp != ct.wantbool && ct.wanterr == "" {
|
||||
errf("want bool %v, got %v", ct.wantbool, *bp)
|
||||
}
|
||||
if !ct.wanttime.IsZero() && !ct.wanttime.Equal(timeValue(ct.d)) {
|
||||
errf("want time %v, got %v", ct.wanttime, timeValue(ct.d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
// nil
|
||||
// []byte
|
||||
// string [*] everywhere except from Rows.Next.
|
||||
// time.Time
|
||||
//
|
||||
package driver
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ValueConverter is the interface providing the ConvertValue method.
|
||||
|
@ -39,7 +40,7 @@ type ValueConverter interface {
|
|||
// 1 is true
|
||||
// 0 is false,
|
||||
// other integers are an error
|
||||
// - for strings and []byte, same rules as strconv.Atob
|
||||
// - for strings and []byte, same rules as strconv.ParseBool
|
||||
// - all other types are an error
|
||||
var Bool boolType
|
||||
|
||||
|
@ -143,9 +144,10 @@ func (stringType) ConvertValue(v interface{}) (interface{}, error) {
|
|||
// bool
|
||||
// nil
|
||||
// []byte
|
||||
// time.Time
|
||||
// string
|
||||
//
|
||||
// This is the ame list as IsScanSubsetType, with the addition of
|
||||
// This is the same list as IsScanSubsetType, with the addition of
|
||||
// string.
|
||||
func IsParameterSubsetType(v interface{}) bool {
|
||||
if IsScanSubsetType(v) {
|
||||
|
@ -165,6 +167,7 @@ func IsParameterSubsetType(v interface{}) bool {
|
|||
// bool
|
||||
// nil
|
||||
// []byte
|
||||
// time.Time
|
||||
//
|
||||
// This is the same list as IsParameterSubsetType, without string.
|
||||
func IsScanSubsetType(v interface{}) bool {
|
||||
|
@ -172,7 +175,7 @@ func IsScanSubsetType(v interface{}) bool {
|
|||
return true
|
||||
}
|
||||
switch v.(type) {
|
||||
case int64, float64, []byte, bool:
|
||||
case int64, float64, []byte, bool, time.Time:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
|
|
@ -7,6 +7,7 @@ package driver
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type valueConverterTest struct {
|
||||
|
@ -16,6 +17,8 @@ type valueConverterTest struct {
|
|||
err string
|
||||
}
|
||||
|
||||
var now = time.Now()
|
||||
|
||||
var valueConverterTests = []valueConverterTest{
|
||||
{Bool, "true", true, ""},
|
||||
{Bool, "True", true, ""},
|
||||
|
@ -33,6 +36,7 @@ var valueConverterTests = []valueConverterTest{
|
|||
{Bool, uint16(0), false, ""},
|
||||
{c: Bool, in: "foo", err: "sql/driver: couldn't convert \"foo\" into type bool"},
|
||||
{c: Bool, in: 2, err: "sql/driver: couldn't convert 2 into type bool"},
|
||||
{DefaultParameterConverter, now, now, ""},
|
||||
}
|
||||
|
||||
func TestValueConverters(t *testing.T) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"exp/sql/driver"
|
||||
)
|
||||
|
@ -77,6 +78,17 @@ type fakeConn struct {
|
|||
db *fakeDB // where to return ourselves to
|
||||
|
||||
currTx *fakeTx
|
||||
|
||||
// Stats for tests:
|
||||
mu sync.Mutex
|
||||
stmtsMade int
|
||||
stmtsClosed int
|
||||
}
|
||||
|
||||
func (c *fakeConn) incrStat(v *int) {
|
||||
c.mu.Lock()
|
||||
*v++
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
type fakeTx struct {
|
||||
|
@ -110,25 +122,34 @@ func init() {
|
|||
|
||||
// Supports dsn forms:
|
||||
// <dbname>
|
||||
// <dbname>;wipe
|
||||
// <dbname>;<opts> (no currently supported options)
|
||||
func (d *fakeDriver) Open(dsn string) (driver.Conn, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
d.openCount++
|
||||
if d.dbs == nil {
|
||||
d.dbs = make(map[string]*fakeDB)
|
||||
}
|
||||
parts := strings.Split(dsn, ";")
|
||||
if len(parts) < 1 {
|
||||
return nil, errors.New("fakedb: no database name")
|
||||
}
|
||||
name := parts[0]
|
||||
|
||||
db := d.getDB(name)
|
||||
|
||||
d.mu.Lock()
|
||||
d.openCount++
|
||||
d.mu.Unlock()
|
||||
return &fakeConn{db: db}, nil
|
||||
}
|
||||
|
||||
func (d *fakeDriver) getDB(name string) *fakeDB {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.dbs == nil {
|
||||
d.dbs = make(map[string]*fakeDB)
|
||||
}
|
||||
db, ok := d.dbs[name]
|
||||
if !ok {
|
||||
db = &fakeDB{name: name}
|
||||
d.dbs[name] = db
|
||||
}
|
||||
return &fakeConn{db: db}, nil
|
||||
return db
|
||||
}
|
||||
|
||||
func (db *fakeDB) wipe() {
|
||||
|
@ -200,7 +221,7 @@ func (c *fakeConn) Close() error {
|
|||
func checkSubsetTypes(args []interface{}) error {
|
||||
for n, arg := range args {
|
||||
switch arg.(type) {
|
||||
case int64, float64, bool, nil, []byte, string:
|
||||
case int64, float64, bool, nil, []byte, string, time.Time:
|
||||
default:
|
||||
return fmt.Errorf("fakedb_test: invalid argument #%d: %v, type %T", n+1, arg, arg)
|
||||
}
|
||||
|
@ -297,6 +318,8 @@ func (c *fakeConn) prepareInsert(stmt *fakeStmt, parts []string) (driver.Stmt, e
|
|||
switch ctype {
|
||||
case "string":
|
||||
subsetVal = []byte(value)
|
||||
case "blob":
|
||||
subsetVal = []byte(value)
|
||||
case "int32":
|
||||
i, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
|
@ -327,6 +350,7 @@ func (c *fakeConn) Prepare(query string) (driver.Stmt, error) {
|
|||
cmd := parts[0]
|
||||
parts = parts[1:]
|
||||
stmt := &fakeStmt{q: query, c: c, cmd: cmd}
|
||||
c.incrStat(&c.stmtsMade)
|
||||
switch cmd {
|
||||
case "WIPE":
|
||||
// Nothing
|
||||
|
@ -347,7 +371,10 @@ func (s *fakeStmt) ColumnConverter(idx int) driver.ValueConverter {
|
|||
}
|
||||
|
||||
func (s *fakeStmt) Close() error {
|
||||
s.closed = true
|
||||
if !s.closed {
|
||||
s.c.incrStat(&s.c.stmtsClosed)
|
||||
s.closed = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -501,9 +528,19 @@ type rowsCursor struct {
|
|||
pos int
|
||||
rows []*row
|
||||
closed bool
|
||||
|
||||
// a clone of slices to give out to clients, indexed by the
|
||||
// the original slice's first byte address. we clone them
|
||||
// just so we're able to corrupt them on close.
|
||||
bytesClone map[*byte][]byte
|
||||
}
|
||||
|
||||
func (rc *rowsCursor) Close() error {
|
||||
if !rc.closed {
|
||||
for _, bs := range rc.bytesClone {
|
||||
bs[0] = 255 // first byte corrupted
|
||||
}
|
||||
}
|
||||
rc.closed = true
|
||||
return nil
|
||||
}
|
||||
|
@ -528,6 +565,19 @@ func (rc *rowsCursor) Next(dest []interface{}) error {
|
|||
// for ease of drivers, and to prevent drivers from
|
||||
// messing up conversions or doing them differently.
|
||||
dest[i] = v
|
||||
|
||||
if bs, ok := v.([]byte); ok {
|
||||
if rc.bytesClone == nil {
|
||||
rc.bytesClone = make(map[*byte][]byte)
|
||||
}
|
||||
clone, ok := rc.bytesClone[&bs[0]]
|
||||
if !ok {
|
||||
clone = make([]byte, len(bs))
|
||||
copy(clone, bs)
|
||||
rc.bytesClone[&bs[0]] = clone
|
||||
}
|
||||
dest[i] = clone
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -540,6 +590,8 @@ func converterForType(typ string) driver.ValueConverter {
|
|||
return driver.Int32
|
||||
case "string":
|
||||
return driver.String
|
||||
case "datetime":
|
||||
return driver.DefaultParameterConverter
|
||||
}
|
||||
panic("invalid fakedb column type of " + typ)
|
||||
}
|
||||
|
|
|
@ -243,8 +243,13 @@ func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
return stmt.Query(args...)
|
||||
rows, err := stmt.Query(args...)
|
||||
if err != nil {
|
||||
stmt.Close()
|
||||
return nil, err
|
||||
}
|
||||
rows.closeStmt = stmt
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
// QueryRow executes a query that is expected to return at most one row.
|
||||
|
@ -549,8 +554,8 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) {
|
|||
// statement, a function to call to release the connection, and a
|
||||
// statement bound to that connection.
|
||||
func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(), si driver.Stmt, err error) {
|
||||
if s.stickyErr != nil {
|
||||
return nil, nil, nil, s.stickyErr
|
||||
if err = s.stickyErr; err != nil {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
if s.closed {
|
||||
|
@ -706,9 +711,10 @@ type Rows struct {
|
|||
releaseConn func()
|
||||
rowsi driver.Rows
|
||||
|
||||
closed bool
|
||||
lastcols []interface{}
|
||||
lasterr error
|
||||
closed bool
|
||||
lastcols []interface{}
|
||||
lasterr error
|
||||
closeStmt *Stmt // if non-nil, statement to Close on close
|
||||
}
|
||||
|
||||
// Next prepares the next result row for reading with the Scan method.
|
||||
|
@ -726,6 +732,9 @@ func (rs *Rows) Next() bool {
|
|||
rs.lastcols = make([]interface{}, len(rs.rowsi.Columns()))
|
||||
}
|
||||
rs.lasterr = rs.rowsi.Next(rs.lastcols)
|
||||
if rs.lasterr == io.EOF {
|
||||
rs.Close()
|
||||
}
|
||||
return rs.lasterr == nil
|
||||
}
|
||||
|
||||
|
@ -786,6 +795,9 @@ func (rs *Rows) Close() error {
|
|||
rs.closed = true
|
||||
err := rs.rowsi.Close()
|
||||
rs.releaseConn()
|
||||
if rs.closeStmt != nil {
|
||||
rs.closeStmt.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -800,10 +812,6 @@ type Row struct {
|
|||
// pointed at by dest. If more than one row matches the query,
|
||||
// Scan uses the first row and discards the rest. If no row matches
|
||||
// the query, Scan returns ErrNoRows.
|
||||
//
|
||||
// If dest contains pointers to []byte, the slices should not be
|
||||
// modified and should only be considered valid until the next call to
|
||||
// Next or Scan.
|
||||
func (r *Row) Scan(dest ...interface{}) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
|
@ -812,7 +820,33 @@ func (r *Row) Scan(dest ...interface{}) error {
|
|||
if !r.rows.Next() {
|
||||
return ErrNoRows
|
||||
}
|
||||
return r.rows.Scan(dest...)
|
||||
err := r.rows.Scan(dest...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(bradfitz): for now we need to defensively clone all
|
||||
// []byte that the driver returned, since we're about to close
|
||||
// the Rows in our defer, when we return from this function.
|
||||
// the contract with the driver.Next(...) interface is that it
|
||||
// can return slices into read-only temporary memory that's
|
||||
// only valid until the next Scan/Close. But the TODO is that
|
||||
// for a lot of drivers, this copy will be unnecessary. We
|
||||
// should provide an optional interface for drivers to
|
||||
// implement to say, "don't worry, the []bytes that I return
|
||||
// from Next will not be modified again." (for instance, if
|
||||
// they were obtained from the network anyway) But for now we
|
||||
// don't care.
|
||||
for _, dp := range dest {
|
||||
b, ok := dp.(*[]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
clone := make([]byte, len(*b))
|
||||
copy(clone, *b)
|
||||
*b = clone
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A Result summarizes an executed SQL command.
|
||||
|
|
|
@ -8,10 +8,15 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const fakeDBName = "foo"
|
||||
|
||||
var chrisBirthday = time.Unix(123456789, 0)
|
||||
|
||||
func newTestDB(t *testing.T, name string) *DB {
|
||||
db, err := Open("test", "foo")
|
||||
db, err := Open("test", fakeDBName)
|
||||
if err != nil {
|
||||
t.Fatalf("Open: %v", err)
|
||||
}
|
||||
|
@ -19,10 +24,10 @@ func newTestDB(t *testing.T, name string) *DB {
|
|||
t.Fatalf("exec wipe: %v", err)
|
||||
}
|
||||
if name == "people" {
|
||||
exec(t, db, "CREATE|people|name=string,age=int32,dead=bool")
|
||||
exec(t, db, "INSERT|people|name=Alice,age=?", 1)
|
||||
exec(t, db, "INSERT|people|name=Bob,age=?", 2)
|
||||
exec(t, db, "INSERT|people|name=Chris,age=?", 3)
|
||||
exec(t, db, "CREATE|people|name=string,age=int32,photo=blob,dead=bool,bdate=datetime")
|
||||
exec(t, db, "INSERT|people|name=Alice,age=?,photo=APHOTO", 1)
|
||||
exec(t, db, "INSERT|people|name=Bob,age=?,photo=BPHOTO", 2)
|
||||
exec(t, db, "INSERT|people|name=Chris,age=?,photo=CPHOTO,bdate=?", 3, chrisBirthday)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
@ -73,6 +78,12 @@ func TestQuery(t *testing.T) {
|
|||
if !reflect.DeepEqual(got, want) {
|
||||
t.Logf(" got: %#v\nwant: %#v", got, want)
|
||||
}
|
||||
|
||||
// And verify that the final rows.Next() call, which hit EOF,
|
||||
// also closed the rows connection.
|
||||
if n := len(db.freeConn); n != 1 {
|
||||
t.Errorf("free conns after query hitting EOF = %d; want 1", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowsColumns(t *testing.T) {
|
||||
|
@ -97,12 +108,18 @@ func TestQueryRow(t *testing.T) {
|
|||
defer closeDB(t, db)
|
||||
var name string
|
||||
var age int
|
||||
var birthday time.Time
|
||||
|
||||
err := db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&age)
|
||||
if err == nil || !strings.Contains(err.Error(), "expected 2 destination arguments") {
|
||||
t.Errorf("expected error from wrong number of arguments; actually got: %v", err)
|
||||
}
|
||||
|
||||
err = db.QueryRow("SELECT|people|bdate|age=?", 3).Scan(&birthday)
|
||||
if err != nil || !birthday.Equal(chrisBirthday) {
|
||||
t.Errorf("chris birthday = %v, err = %v; want %v", birthday, err, chrisBirthday)
|
||||
}
|
||||
|
||||
err = db.QueryRow("SELECT|people|age,name|age=?", 2).Scan(&age, &name)
|
||||
if err != nil {
|
||||
t.Fatalf("age QueryRow+Scan: %v", err)
|
||||
|
@ -124,6 +141,16 @@ func TestQueryRow(t *testing.T) {
|
|||
if age != 1 {
|
||||
t.Errorf("expected age 1, got %d", age)
|
||||
}
|
||||
|
||||
var photo []byte
|
||||
err = db.QueryRow("SELECT|people|photo|name=?", "Alice").Scan(&photo)
|
||||
if err != nil {
|
||||
t.Fatalf("photo QueryRow+Scan: %v", err)
|
||||
}
|
||||
want := []byte("APHOTO")
|
||||
if !reflect.DeepEqual(photo, want) {
|
||||
t.Errorf("photo = %q; want %q", photo, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatementErrorAfterClose(t *testing.T) {
|
||||
|
@ -258,3 +285,21 @@ func TestIssue2542Deadlock(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryRowClosingStmt(t *testing.T) {
|
||||
db := newTestDB(t, "people")
|
||||
defer closeDB(t, db)
|
||||
var name string
|
||||
var age int
|
||||
err := db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&age, &name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(db.freeConn) != 1 {
|
||||
t.Fatalf("expected 1 free conn")
|
||||
}
|
||||
fakeConn := db.freeConn[0].(*fakeConn)
|
||||
if made, closed := fakeConn.stmtsMade, fakeConn.stmtsClosed; made != closed {
|
||||
t.Logf("statement close mismatch: made %d, closed %d", made, closed)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -420,27 +420,37 @@ type chanWriter struct {
|
|||
}
|
||||
|
||||
// Write writes data to the remote process's standard input.
|
||||
func (w *chanWriter) Write(data []byte) (n int, err error) {
|
||||
for {
|
||||
if w.rwin == 0 {
|
||||
func (w *chanWriter) Write(data []byte) (written int, err error) {
|
||||
for len(data) > 0 {
|
||||
for w.rwin < 1 {
|
||||
win, ok := <-w.win
|
||||
if !ok {
|
||||
return 0, io.EOF
|
||||
}
|
||||
w.rwin += win
|
||||
continue
|
||||
}
|
||||
n := min(len(data), w.rwin)
|
||||
peersId := w.clientChan.peersId
|
||||
n = len(data)
|
||||
packet := make([]byte, 0, 9+n)
|
||||
packet = append(packet, msgChannelData,
|
||||
byte(peersId>>24), byte(peersId>>16), byte(peersId>>8), byte(peersId),
|
||||
byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
||||
err = w.clientChan.writePacket(append(packet, data...))
|
||||
packet := []byte{
|
||||
msgChannelData,
|
||||
byte(peersId >> 24), byte(peersId >> 16), byte(peersId >> 8), byte(peersId),
|
||||
byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n),
|
||||
}
|
||||
if err = w.clientChan.writePacket(append(packet, data[:n]...)); err != nil {
|
||||
break
|
||||
}
|
||||
data = data[n:]
|
||||
w.rwin -= n
|
||||
return
|
||||
written += n
|
||||
}
|
||||
panic("unreachable")
|
||||
return
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (w *chanWriter) Close() error {
|
||||
|
|
|
@ -14,7 +14,7 @@ others.
|
|||
An SSH server is represented by a ServerConfig, which holds certificate
|
||||
details and handles authentication of ServerConns.
|
||||
|
||||
config := new(ServerConfig)
|
||||
config := new(ssh.ServerConfig)
|
||||
config.PubKeyCallback = pubKeyAuth
|
||||
config.PasswordCallback = passwordAuth
|
||||
|
||||
|
@ -34,8 +34,7 @@ Once a ServerConfig has been configured, connections can be accepted.
|
|||
if err != nil {
|
||||
panic("failed to accept incoming connection")
|
||||
}
|
||||
err = sConn.Handshake(conn)
|
||||
if err != nil {
|
||||
if err := sConn.Handshake(conn); err != nil {
|
||||
panic("failed to handshake")
|
||||
}
|
||||
|
||||
|
@ -60,16 +59,20 @@ the case of a shell, the type is "session" and ServerShell may be used to
|
|||
present a simple terminal interface.
|
||||
|
||||
if channel.ChannelType() != "session" {
|
||||
c.Reject(UnknownChannelType, "unknown channel type")
|
||||
channel.Reject(UnknownChannelType, "unknown channel type")
|
||||
return
|
||||
}
|
||||
channel.Accept()
|
||||
|
||||
shell := NewServerShell(channel, "> ")
|
||||
term := terminal.NewTerminal(channel, "> ")
|
||||
serverTerm := &ssh.ServerTerminal{
|
||||
Term: term,
|
||||
Channel: channel,
|
||||
}
|
||||
go func() {
|
||||
defer channel.Close()
|
||||
for {
|
||||
line, err := shell.ReadLine()
|
||||
line, err := serverTerm.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
@ -78,8 +81,27 @@ present a simple terminal interface.
|
|||
return
|
||||
}()
|
||||
|
||||
To authenticate with the remote server you must pass at least one implementation of
|
||||
ClientAuth via the Auth field in ClientConfig.
|
||||
|
||||
// password implements the ClientPassword interface
|
||||
type password string
|
||||
|
||||
func (p password) Password(user string) (string, error) {
|
||||
return string(p), nil
|
||||
}
|
||||
|
||||
config := &ssh.ClientConfig {
|
||||
User: "username",
|
||||
Auth: []ClientAuth {
|
||||
// ClientAuthPassword wraps a ClientPassword implementation
|
||||
// in a type that implements ClientAuth.
|
||||
ClientAuthPassword(password("yourpassword")),
|
||||
}
|
||||
}
|
||||
|
||||
An SSH client is represented with a ClientConn. Currently only the "password"
|
||||
authentication method is supported.
|
||||
authentication method is supported.
|
||||
|
||||
config := &ClientConfig{
|
||||
User: "username",
|
||||
|
@ -87,19 +109,19 @@ authentication method is supported.
|
|||
}
|
||||
client, err := Dial("yourserver.com:22", config)
|
||||
|
||||
Each ClientConn can support multiple interactive sessions, represented by a Session.
|
||||
Each ClientConn can support multiple interactive sessions, represented by a Session.
|
||||
|
||||
session, err := client.NewSession()
|
||||
|
||||
Once a Session is created, you can execute a single command on the remote side
|
||||
using the Run method.
|
||||
Once a Session is created, you can execute a single command on the remote side
|
||||
using the Exec method.
|
||||
|
||||
b := bytes.NewBuffer()
|
||||
session.Stdin = b
|
||||
if err := session.Run("/usr/bin/whoami"); err != nil {
|
||||
panic("Failed to exec: " + err.String())
|
||||
}
|
||||
reader := bufio.NewReader(session.Stdin)
|
||||
line, _, _ := reader.ReadLine()
|
||||
fmt.Println(line)
|
||||
fmt.Println(bytes.String())
|
||||
session.Close()
|
||||
*/
|
||||
package ssh
|
||||
|
|
|
@ -1,398 +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 "io"
|
||||
|
||||
// ServerShell contains the state for running a VT100 terminal that is capable
|
||||
// of reading lines of input.
|
||||
type ServerShell struct {
|
||||
c Channel
|
||||
prompt string
|
||||
|
||||
// line is the current line being entered.
|
||||
line []byte
|
||||
// pos is the logical position of the cursor in line
|
||||
pos int
|
||||
|
||||
// cursorX contains the current X value of the cursor where the left
|
||||
// edge is 0. cursorY contains the row number where the first row of
|
||||
// the current line is 0.
|
||||
cursorX, cursorY int
|
||||
// maxLine is the greatest value of cursorY so far.
|
||||
maxLine int
|
||||
|
||||
termWidth, termHeight int
|
||||
|
||||
// outBuf contains the terminal data to be sent.
|
||||
outBuf []byte
|
||||
// remainder contains the remainder of any partial key sequences after
|
||||
// a read. It aliases into inBuf.
|
||||
remainder []byte
|
||||
inBuf [256]byte
|
||||
}
|
||||
|
||||
// NewServerShell runs a VT100 terminal on the given channel. prompt is a
|
||||
// string that is written at the start of each input line. For example: "> ".
|
||||
func NewServerShell(c Channel, prompt string) *ServerShell {
|
||||
return &ServerShell{
|
||||
c: c,
|
||||
prompt: prompt,
|
||||
termWidth: 80,
|
||||
termHeight: 24,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
keyCtrlD = 4
|
||||
keyEnter = '\r'
|
||||
keyEscape = 27
|
||||
keyBackspace = 127
|
||||
keyUnknown = 256 + iota
|
||||
keyUp
|
||||
keyDown
|
||||
keyLeft
|
||||
keyRight
|
||||
keyAltLeft
|
||||
keyAltRight
|
||||
)
|
||||
|
||||
// bytesToKey tries to parse a key sequence from b. If successful, it returns
|
||||
// the key and the remainder of the input. Otherwise it returns -1.
|
||||
func bytesToKey(b []byte) (int, []byte) {
|
||||
if len(b) == 0 {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
if b[0] != keyEscape {
|
||||
return int(b[0]), b[1:]
|
||||
}
|
||||
|
||||
if len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
|
||||
switch b[2] {
|
||||
case 'A':
|
||||
return keyUp, b[3:]
|
||||
case 'B':
|
||||
return keyDown, b[3:]
|
||||
case 'C':
|
||||
return keyRight, b[3:]
|
||||
case 'D':
|
||||
return keyLeft, b[3:]
|
||||
}
|
||||
}
|
||||
|
||||
if len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
||||
switch b[5] {
|
||||
case 'C':
|
||||
return keyAltRight, b[6:]
|
||||
case 'D':
|
||||
return keyAltLeft, b[6:]
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here then we have a key that we don't recognise, or a
|
||||
// partial sequence. It's not clear how one should find the end of a
|
||||
// sequence without knowing them all, but it seems that [a-zA-Z] only
|
||||
// appears at the end of a sequence.
|
||||
for i, c := range b[0:] {
|
||||
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
|
||||
return keyUnknown, b[i+1:]
|
||||
}
|
||||
}
|
||||
|
||||
return -1, b
|
||||
}
|
||||
|
||||
// queue appends data to the end of ss.outBuf
|
||||
func (ss *ServerShell) queue(data []byte) {
|
||||
if len(ss.outBuf)+len(data) > cap(ss.outBuf) {
|
||||
newOutBuf := make([]byte, len(ss.outBuf), 2*(len(ss.outBuf)+len(data)))
|
||||
copy(newOutBuf, ss.outBuf)
|
||||
ss.outBuf = newOutBuf
|
||||
}
|
||||
|
||||
oldLen := len(ss.outBuf)
|
||||
ss.outBuf = ss.outBuf[:len(ss.outBuf)+len(data)]
|
||||
copy(ss.outBuf[oldLen:], data)
|
||||
}
|
||||
|
||||
var eraseUnderCursor = []byte{' ', keyEscape, '[', 'D'}
|
||||
|
||||
func isPrintable(key int) bool {
|
||||
return key >= 32 && key < 127
|
||||
}
|
||||
|
||||
// moveCursorToPos appends data to ss.outBuf which will move the cursor to the
|
||||
// given, logical position in the text.
|
||||
func (ss *ServerShell) moveCursorToPos(pos int) {
|
||||
x := len(ss.prompt) + pos
|
||||
y := x / ss.termWidth
|
||||
x = x % ss.termWidth
|
||||
|
||||
up := 0
|
||||
if y < ss.cursorY {
|
||||
up = ss.cursorY - y
|
||||
}
|
||||
|
||||
down := 0
|
||||
if y > ss.cursorY {
|
||||
down = y - ss.cursorY
|
||||
}
|
||||
|
||||
left := 0
|
||||
if x < ss.cursorX {
|
||||
left = ss.cursorX - x
|
||||
}
|
||||
|
||||
right := 0
|
||||
if x > ss.cursorX {
|
||||
right = x - ss.cursorX
|
||||
}
|
||||
|
||||
movement := make([]byte, 3*(up+down+left+right))
|
||||
m := movement
|
||||
for i := 0; i < up; i++ {
|
||||
m[0] = keyEscape
|
||||
m[1] = '['
|
||||
m[2] = 'A'
|
||||
m = m[3:]
|
||||
}
|
||||
for i := 0; i < down; i++ {
|
||||
m[0] = keyEscape
|
||||
m[1] = '['
|
||||
m[2] = 'B'
|
||||
m = m[3:]
|
||||
}
|
||||
for i := 0; i < left; i++ {
|
||||
m[0] = keyEscape
|
||||
m[1] = '['
|
||||
m[2] = 'D'
|
||||
m = m[3:]
|
||||
}
|
||||
for i := 0; i < right; i++ {
|
||||
m[0] = keyEscape
|
||||
m[1] = '['
|
||||
m[2] = 'C'
|
||||
m = m[3:]
|
||||
}
|
||||
|
||||
ss.cursorX = x
|
||||
ss.cursorY = y
|
||||
ss.queue(movement)
|
||||
}
|
||||
|
||||
const maxLineLength = 4096
|
||||
|
||||
// handleKey processes the given key and, optionally, returns a line of text
|
||||
// that the user has entered.
|
||||
func (ss *ServerShell) handleKey(key int) (line string, ok bool) {
|
||||
switch key {
|
||||
case keyBackspace:
|
||||
if ss.pos == 0 {
|
||||
return
|
||||
}
|
||||
ss.pos--
|
||||
|
||||
copy(ss.line[ss.pos:], ss.line[1+ss.pos:])
|
||||
ss.line = ss.line[:len(ss.line)-1]
|
||||
ss.writeLine(ss.line[ss.pos:])
|
||||
ss.moveCursorToPos(ss.pos)
|
||||
ss.queue(eraseUnderCursor)
|
||||
case keyAltLeft:
|
||||
// move left by a word.
|
||||
if ss.pos == 0 {
|
||||
return
|
||||
}
|
||||
ss.pos--
|
||||
for ss.pos > 0 {
|
||||
if ss.line[ss.pos] != ' ' {
|
||||
break
|
||||
}
|
||||
ss.pos--
|
||||
}
|
||||
for ss.pos > 0 {
|
||||
if ss.line[ss.pos] == ' ' {
|
||||
ss.pos++
|
||||
break
|
||||
}
|
||||
ss.pos--
|
||||
}
|
||||
ss.moveCursorToPos(ss.pos)
|
||||
case keyAltRight:
|
||||
// move right by a word.
|
||||
for ss.pos < len(ss.line) {
|
||||
if ss.line[ss.pos] == ' ' {
|
||||
break
|
||||
}
|
||||
ss.pos++
|
||||
}
|
||||
for ss.pos < len(ss.line) {
|
||||
if ss.line[ss.pos] != ' ' {
|
||||
break
|
||||
}
|
||||
ss.pos++
|
||||
}
|
||||
ss.moveCursorToPos(ss.pos)
|
||||
case keyLeft:
|
||||
if ss.pos == 0 {
|
||||
return
|
||||
}
|
||||
ss.pos--
|
||||
ss.moveCursorToPos(ss.pos)
|
||||
case keyRight:
|
||||
if ss.pos == len(ss.line) {
|
||||
return
|
||||
}
|
||||
ss.pos++
|
||||
ss.moveCursorToPos(ss.pos)
|
||||
case keyEnter:
|
||||
ss.moveCursorToPos(len(ss.line))
|
||||
ss.queue([]byte("\r\n"))
|
||||
line = string(ss.line)
|
||||
ok = true
|
||||
ss.line = ss.line[:0]
|
||||
ss.pos = 0
|
||||
ss.cursorX = 0
|
||||
ss.cursorY = 0
|
||||
ss.maxLine = 0
|
||||
default:
|
||||
if !isPrintable(key) {
|
||||
return
|
||||
}
|
||||
if len(ss.line) == maxLineLength {
|
||||
return
|
||||
}
|
||||
if len(ss.line) == cap(ss.line) {
|
||||
newLine := make([]byte, len(ss.line), 2*(1+len(ss.line)))
|
||||
copy(newLine, ss.line)
|
||||
ss.line = newLine
|
||||
}
|
||||
ss.line = ss.line[:len(ss.line)+1]
|
||||
copy(ss.line[ss.pos+1:], ss.line[ss.pos:])
|
||||
ss.line[ss.pos] = byte(key)
|
||||
ss.writeLine(ss.line[ss.pos:])
|
||||
ss.pos++
|
||||
ss.moveCursorToPos(ss.pos)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ss *ServerShell) writeLine(line []byte) {
|
||||
for len(line) != 0 {
|
||||
if ss.cursorX == ss.termWidth {
|
||||
ss.queue([]byte("\r\n"))
|
||||
ss.cursorX = 0
|
||||
ss.cursorY++
|
||||
if ss.cursorY > ss.maxLine {
|
||||
ss.maxLine = ss.cursorY
|
||||
}
|
||||
}
|
||||
|
||||
remainingOnLine := ss.termWidth - ss.cursorX
|
||||
todo := len(line)
|
||||
if todo > remainingOnLine {
|
||||
todo = remainingOnLine
|
||||
}
|
||||
ss.queue(line[:todo])
|
||||
ss.cursorX += todo
|
||||
line = line[todo:]
|
||||
}
|
||||
}
|
||||
|
||||
// parsePtyRequest parses the payload of the pty-req message and extracts the
|
||||
// dimensions of the terminal. See RFC 4254, section 6.2.
|
||||
func parsePtyRequest(s []byte) (width, height int, ok bool) {
|
||||
_, s, ok = parseString(s)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
width32, s, ok := parseUint32(s)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
height32, _, ok := parseUint32(s)
|
||||
width = int(width32)
|
||||
height = int(height32)
|
||||
if width < 1 {
|
||||
ok = false
|
||||
}
|
||||
if height < 1 {
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ss *ServerShell) Write(buf []byte) (n int, err error) {
|
||||
return ss.c.Write(buf)
|
||||
}
|
||||
|
||||
// ReadLine returns a line of input from the terminal.
|
||||
func (ss *ServerShell) ReadLine() (line string, err error) {
|
||||
ss.writeLine([]byte(ss.prompt))
|
||||
ss.c.Write(ss.outBuf)
|
||||
ss.outBuf = ss.outBuf[:0]
|
||||
|
||||
for {
|
||||
// ss.remainder is a slice at the beginning of ss.inBuf
|
||||
// containing a partial key sequence
|
||||
readBuf := ss.inBuf[len(ss.remainder):]
|
||||
var n int
|
||||
n, err = ss.c.Read(readBuf)
|
||||
if err == nil {
|
||||
ss.remainder = ss.inBuf[:n+len(ss.remainder)]
|
||||
rest := ss.remainder
|
||||
lineOk := false
|
||||
for !lineOk {
|
||||
var key int
|
||||
key, rest = bytesToKey(rest)
|
||||
if key < 0 {
|
||||
break
|
||||
}
|
||||
if key == keyCtrlD {
|
||||
return "", io.EOF
|
||||
}
|
||||
line, lineOk = ss.handleKey(key)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
n := copy(ss.inBuf[:], rest)
|
||||
ss.remainder = ss.inBuf[:n]
|
||||
} else {
|
||||
ss.remainder = nil
|
||||
}
|
||||
ss.c.Write(ss.outBuf)
|
||||
ss.outBuf = ss.outBuf[:0]
|
||||
if lineOk {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if req, ok := err.(ChannelRequest); ok {
|
||||
ok := false
|
||||
switch req.Request {
|
||||
case "pty-req":
|
||||
ss.termWidth, ss.termHeight, ok = parsePtyRequest(req.Payload)
|
||||
if !ok {
|
||||
ss.termWidth = 80
|
||||
ss.termHeight = 24
|
||||
}
|
||||
case "shell":
|
||||
ok = true
|
||||
if len(req.Payload) > 0 {
|
||||
// We don't accept any commands, only the default shell.
|
||||
ok = false
|
||||
}
|
||||
case "env":
|
||||
ok = true
|
||||
}
|
||||
if req.WantReply {
|
||||
ss.c.AckRequest(ok)
|
||||
}
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
|
@ -1,134 +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 (
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type MockChannel struct {
|
||||
toSend []byte
|
||||
bytesPerRead int
|
||||
received []byte
|
||||
}
|
||||
|
||||
func (c *MockChannel) Accept() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MockChannel) Reject(RejectionReason, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MockChannel) Read(data []byte) (n int, err error) {
|
||||
n = len(data)
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
if n > len(c.toSend) {
|
||||
n = len(c.toSend)
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if c.bytesPerRead > 0 && n > c.bytesPerRead {
|
||||
n = c.bytesPerRead
|
||||
}
|
||||
copy(data, c.toSend[:n])
|
||||
c.toSend = c.toSend[n:]
|
||||
return
|
||||
}
|
||||
|
||||
func (c *MockChannel) Write(data []byte) (n int, err error) {
|
||||
c.received = append(c.received, data...)
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
func (c *MockChannel) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MockChannel) AckRequest(ok bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MockChannel) ChannelType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *MockChannel) ExtraData() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestClose(t *testing.T) {
|
||||
c := &MockChannel{}
|
||||
ss := NewServerShell(c, "> ")
|
||||
line, err := ss.ReadLine()
|
||||
if line != "" {
|
||||
t.Errorf("Expected empty line but got: %s", line)
|
||||
}
|
||||
if err != io.EOF {
|
||||
t.Errorf("Error should have been EOF but got: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var keyPressTests = []struct {
|
||||
in string
|
||||
line string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
"",
|
||||
"",
|
||||
io.EOF,
|
||||
},
|
||||
{
|
||||
"\r",
|
||||
"",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"foo\r",
|
||||
"foo",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"a\x1b[Cb\r", // right
|
||||
"ab",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"a\x1b[Db\r", // left
|
||||
"ba",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"a\177b\r", // backspace
|
||||
"b",
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
func TestKeyPresses(t *testing.T) {
|
||||
for i, test := range keyPressTests {
|
||||
for j := 0; j < len(test.in); j++ {
|
||||
c := &MockChannel{
|
||||
toSend: []byte(test.in),
|
||||
bytesPerRead: j,
|
||||
}
|
||||
ss := NewServerShell(c, "> ")
|
||||
line, err := ss.ReadLine()
|
||||
if line != test.line {
|
||||
t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line)
|
||||
break
|
||||
}
|
||||
if err != test.err {
|
||||
t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
81
libgo/go/exp/ssh/server_terminal.go
Normal file
81
libgo/go/exp/ssh/server_terminal.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
// 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
|
||||
|
||||
// A Terminal is capable of parsing and generating virtual terminal
|
||||
// data from an SSH client.
|
||||
type Terminal interface {
|
||||
ReadLine() (line string, err error)
|
||||
SetSize(x, y int)
|
||||
Write([]byte) (int, error)
|
||||
}
|
||||
|
||||
// ServerTerminal contains the state for running a terminal that is capable of
|
||||
// reading lines of input.
|
||||
type ServerTerminal struct {
|
||||
Term Terminal
|
||||
Channel Channel
|
||||
}
|
||||
|
||||
// parsePtyRequest parses the payload of the pty-req message and extracts the
|
||||
// dimensions of the terminal. See RFC 4254, section 6.2.
|
||||
func parsePtyRequest(s []byte) (width, height int, ok bool) {
|
||||
_, s, ok = parseString(s)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
width32, s, ok := parseUint32(s)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
height32, _, ok := parseUint32(s)
|
||||
width = int(width32)
|
||||
height = int(height32)
|
||||
if width < 1 {
|
||||
ok = false
|
||||
}
|
||||
if height < 1 {
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ss *ServerTerminal) Write(buf []byte) (n int, err error) {
|
||||
return ss.Term.Write(buf)
|
||||
}
|
||||
|
||||
// ReadLine returns a line of input from the terminal.
|
||||
func (ss *ServerTerminal) ReadLine() (line string, err error) {
|
||||
for {
|
||||
if line, err = ss.Term.ReadLine(); err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
req, ok := err.(ChannelRequest)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ok = false
|
||||
switch req.Request {
|
||||
case "pty-req":
|
||||
var width, height int
|
||||
width, height, ok = parsePtyRequest(req.Payload)
|
||||
ss.Term.SetSize(width, height)
|
||||
case "shell":
|
||||
ok = true
|
||||
if len(req.Payload) > 0 {
|
||||
// We don't accept any commands, only the default shell.
|
||||
ok = false
|
||||
}
|
||||
case "env":
|
||||
ok = true
|
||||
}
|
||||
if req.WantReply {
|
||||
ss.Channel.AckRequest(ok)
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
|
@ -8,6 +8,7 @@ package ssh
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"exp/terminal"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
@ -290,24 +291,32 @@ type exitSignalMsg struct {
|
|||
Lang string
|
||||
}
|
||||
|
||||
func newServerShell(ch *channel, prompt string) *ServerTerminal {
|
||||
term := terminal.NewTerminal(ch, prompt)
|
||||
return &ServerTerminal{
|
||||
Term: term,
|
||||
Channel: ch,
|
||||
}
|
||||
}
|
||||
|
||||
func exitStatusZeroHandler(ch *channel) {
|
||||
defer ch.Close()
|
||||
// this string is returned to stdout
|
||||
shell := NewServerShell(ch, "> ")
|
||||
shell := newServerShell(ch, "> ")
|
||||
shell.ReadLine()
|
||||
sendStatus(0, ch)
|
||||
}
|
||||
|
||||
func exitStatusNonZeroHandler(ch *channel) {
|
||||
defer ch.Close()
|
||||
shell := NewServerShell(ch, "> ")
|
||||
shell := newServerShell(ch, "> ")
|
||||
shell.ReadLine()
|
||||
sendStatus(15, ch)
|
||||
}
|
||||
|
||||
func exitSignalAndStatusHandler(ch *channel) {
|
||||
defer ch.Close()
|
||||
shell := NewServerShell(ch, "> ")
|
||||
shell := newServerShell(ch, "> ")
|
||||
shell.ReadLine()
|
||||
sendStatus(15, ch)
|
||||
sendSignal("TERM", ch)
|
||||
|
@ -315,28 +324,28 @@ func exitSignalAndStatusHandler(ch *channel) {
|
|||
|
||||
func exitSignalHandler(ch *channel) {
|
||||
defer ch.Close()
|
||||
shell := NewServerShell(ch, "> ")
|
||||
shell := newServerShell(ch, "> ")
|
||||
shell.ReadLine()
|
||||
sendSignal("TERM", ch)
|
||||
}
|
||||
|
||||
func exitSignalUnknownHandler(ch *channel) {
|
||||
defer ch.Close()
|
||||
shell := NewServerShell(ch, "> ")
|
||||
shell := newServerShell(ch, "> ")
|
||||
shell.ReadLine()
|
||||
sendSignal("SYS", ch)
|
||||
}
|
||||
|
||||
func exitWithoutSignalOrStatus(ch *channel) {
|
||||
defer ch.Close()
|
||||
shell := NewServerShell(ch, "> ")
|
||||
shell := newServerShell(ch, "> ")
|
||||
shell.ReadLine()
|
||||
}
|
||||
|
||||
func shellHandler(ch *channel) {
|
||||
defer ch.Close()
|
||||
// this string is returned to stdout
|
||||
shell := NewServerShell(ch, "golang")
|
||||
shell := newServerShell(ch, "golang")
|
||||
shell.ReadLine()
|
||||
sendStatus(0, ch)
|
||||
}
|
||||
|
|
|
@ -117,9 +117,7 @@ func (r *reader) readOnePacket() ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
mac := packet[length-1:]
|
||||
if r.cipher != nil {
|
||||
r.cipher.XORKeyStream(packet, packet[:length-1])
|
||||
}
|
||||
r.cipher.XORKeyStream(packet, packet[:length-1])
|
||||
|
||||
if r.mac != nil {
|
||||
r.mac.Write(packet[:length-1])
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
|
@ -463,6 +461,31 @@ func (t *Terminal) readLine() (line string, err error) {
|
|||
}
|
||||
|
||||
for {
|
||||
rest := t.remainder
|
||||
lineOk := false
|
||||
for !lineOk {
|
||||
var key int
|
||||
key, rest = bytesToKey(rest)
|
||||
if key < 0 {
|
||||
break
|
||||
}
|
||||
if key == keyCtrlD {
|
||||
return "", io.EOF
|
||||
}
|
||||
line, lineOk = t.handleKey(key)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
n := copy(t.inBuf[:], rest)
|
||||
t.remainder = t.inBuf[:n]
|
||||
} else {
|
||||
t.remainder = nil
|
||||
}
|
||||
t.c.Write(t.outBuf)
|
||||
t.outBuf = t.outBuf[:0]
|
||||
if lineOk {
|
||||
return
|
||||
}
|
||||
|
||||
// t.remainder is a slice at the beginning of t.inBuf
|
||||
// containing a partial key sequence
|
||||
readBuf := t.inBuf[len(t.remainder):]
|
||||
|
@ -476,38 +499,19 @@ func (t *Terminal) readLine() (line string, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
t.remainder = t.inBuf[:n+len(t.remainder)]
|
||||
rest := t.remainder
|
||||
lineOk := false
|
||||
for !lineOk {
|
||||
var key int
|
||||
key, rest = bytesToKey(rest)
|
||||
if key < 0 {
|
||||
break
|
||||
}
|
||||
if key == keyCtrlD {
|
||||
return "", io.EOF
|
||||
}
|
||||
line, lineOk = t.handleKey(key)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
n := copy(t.inBuf[:], rest)
|
||||
t.remainder = t.inBuf[:n]
|
||||
} else {
|
||||
t.remainder = nil
|
||||
}
|
||||
t.c.Write(t.outBuf)
|
||||
t.outBuf = t.outBuf[:0]
|
||||
if lineOk {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
t.remainder = t.inBuf[:n+len(t.remainder)]
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// SetPrompt sets the prompt to be used when reading subsequent lines.
|
||||
func (t *Terminal) SetPrompt(prompt string) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
t.prompt = prompt
|
||||
}
|
||||
|
||||
func (t *Terminal) SetSize(width, height int) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
|
|
|
@ -111,7 +111,7 @@ func expectedErrors(t *testing.T, testname string, files map[string]*ast.File) m
|
|||
// set otherwise the position information returned here will
|
||||
// not match the position information collected by the parser
|
||||
s.Init(getFile(filename), src, nil, scanner.ScanComments)
|
||||
var prev token.Pos // position of last non-comment token
|
||||
var prev token.Pos // position of last non-comment, non-semicolon token
|
||||
|
||||
scanFile:
|
||||
for {
|
||||
|
@ -124,6 +124,12 @@ func expectedErrors(t *testing.T, testname string, files map[string]*ast.File) m
|
|||
if len(s) == 2 {
|
||||
errors[prev] = string(s[1])
|
||||
}
|
||||
case token.SEMICOLON:
|
||||
// ignore automatically inserted semicolon
|
||||
if lit == "\n" {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
prev = pos
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ func define(kind ast.ObjKind, name string) *ast.Object {
|
|||
if scope.Insert(obj) != nil {
|
||||
panic("types internal error: double declaration")
|
||||
}
|
||||
obj.Decl = scope
|
||||
return obj
|
||||
}
|
||||
|
||||
|
|
|
@ -65,12 +65,13 @@ import (
|
|||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrHelp is the error returned if the flag -help is invoked but no such flag is defined.
|
||||
var ErrHelp = errors.New("flag: help requested")
|
||||
|
||||
// -- Bool Value
|
||||
// -- bool Value
|
||||
type boolValue bool
|
||||
|
||||
func newBoolValue(val bool, p *bool) *boolValue {
|
||||
|
@ -78,15 +79,15 @@ func newBoolValue(val bool, p *bool) *boolValue {
|
|||
return (*boolValue)(p)
|
||||
}
|
||||
|
||||
func (b *boolValue) Set(s string) bool {
|
||||
func (b *boolValue) Set(s string) error {
|
||||
v, err := strconv.ParseBool(s)
|
||||
*b = boolValue(v)
|
||||
return err == nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) }
|
||||
|
||||
// -- Int Value
|
||||
// -- int Value
|
||||
type intValue int
|
||||
|
||||
func newIntValue(val int, p *int) *intValue {
|
||||
|
@ -94,15 +95,15 @@ func newIntValue(val int, p *int) *intValue {
|
|||
return (*intValue)(p)
|
||||
}
|
||||
|
||||
func (i *intValue) Set(s string) bool {
|
||||
func (i *intValue) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 64)
|
||||
*i = intValue(v)
|
||||
return err == nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *intValue) String() string { return fmt.Sprintf("%v", *i) }
|
||||
|
||||
// -- Int64 Value
|
||||
// -- int64 Value
|
||||
type int64Value int64
|
||||
|
||||
func newInt64Value(val int64, p *int64) *int64Value {
|
||||
|
@ -110,15 +111,15 @@ func newInt64Value(val int64, p *int64) *int64Value {
|
|||
return (*int64Value)(p)
|
||||
}
|
||||
|
||||
func (i *int64Value) Set(s string) bool {
|
||||
func (i *int64Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 64)
|
||||
*i = int64Value(v)
|
||||
return err == nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *int64Value) String() string { return fmt.Sprintf("%v", *i) }
|
||||
|
||||
// -- Uint Value
|
||||
// -- uint Value
|
||||
type uintValue uint
|
||||
|
||||
func newUintValue(val uint, p *uint) *uintValue {
|
||||
|
@ -126,10 +127,10 @@ func newUintValue(val uint, p *uint) *uintValue {
|
|||
return (*uintValue)(p)
|
||||
}
|
||||
|
||||
func (i *uintValue) Set(s string) bool {
|
||||
func (i *uintValue) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 64)
|
||||
*i = uintValue(v)
|
||||
return err == nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *uintValue) String() string { return fmt.Sprintf("%v", *i) }
|
||||
|
@ -142,10 +143,10 @@ func newUint64Value(val uint64, p *uint64) *uint64Value {
|
|||
return (*uint64Value)(p)
|
||||
}
|
||||
|
||||
func (i *uint64Value) Set(s string) bool {
|
||||
func (i *uint64Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 64)
|
||||
*i = uint64Value(v)
|
||||
return err == nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *uint64Value) String() string { return fmt.Sprintf("%v", *i) }
|
||||
|
@ -158,14 +159,14 @@ func newStringValue(val string, p *string) *stringValue {
|
|||
return (*stringValue)(p)
|
||||
}
|
||||
|
||||
func (s *stringValue) Set(val string) bool {
|
||||
func (s *stringValue) Set(val string) error {
|
||||
*s = stringValue(val)
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringValue) String() string { return fmt.Sprintf("%s", *s) }
|
||||
|
||||
// -- Float64 Value
|
||||
// -- float64 Value
|
||||
type float64Value float64
|
||||
|
||||
func newFloat64Value(val float64, p *float64) *float64Value {
|
||||
|
@ -173,19 +174,35 @@ func newFloat64Value(val float64, p *float64) *float64Value {
|
|||
return (*float64Value)(p)
|
||||
}
|
||||
|
||||
func (f *float64Value) Set(s string) bool {
|
||||
func (f *float64Value) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
*f = float64Value(v)
|
||||
return err == nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
// -- time.Duration Value
|
||||
type durationValue time.Duration
|
||||
|
||||
func newDurationValue(val time.Duration, p *time.Duration) *durationValue {
|
||||
*p = val
|
||||
return (*durationValue)(p)
|
||||
}
|
||||
|
||||
func (d *durationValue) Set(s string) error {
|
||||
v, err := time.ParseDuration(s)
|
||||
*d = durationValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
|
||||
|
||||
// Value is the interface to the dynamic value stored in a flag.
|
||||
// (The default value is represented as a string.)
|
||||
type Value interface {
|
||||
String() string
|
||||
Set(string) bool
|
||||
Set(string) error
|
||||
}
|
||||
|
||||
// ErrorHandling defines how to handle flag parsing errors.
|
||||
|
@ -276,27 +293,25 @@ func Lookup(name string) *Flag {
|
|||
return commandLine.formal[name]
|
||||
}
|
||||
|
||||
// Set sets the value of the named flag. It returns true if the set succeeded; false if
|
||||
// there is no such flag defined.
|
||||
func (f *FlagSet) Set(name, value string) bool {
|
||||
// Set sets the value of the named flag.
|
||||
func (f *FlagSet) Set(name, value string) error {
|
||||
flag, ok := f.formal[name]
|
||||
if !ok {
|
||||
return false
|
||||
return fmt.Errorf("no such flag -%v", name)
|
||||
}
|
||||
ok = flag.Value.Set(value)
|
||||
if !ok {
|
||||
return false
|
||||
err := flag.Value.Set(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.actual == nil {
|
||||
f.actual = make(map[string]*Flag)
|
||||
}
|
||||
f.actual[name] = flag
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set sets the value of the named command-line flag. It returns true if the
|
||||
// set succeeded; false if there is no such flag defined.
|
||||
func Set(name, value string) bool {
|
||||
// Set sets the value of the named command-line flag.
|
||||
func Set(name, value string) error {
|
||||
return commandLine.Set(name, value)
|
||||
}
|
||||
|
||||
|
@ -543,12 +558,38 @@ func (f *FlagSet) Float64(name string, value float64, usage string) *float64 {
|
|||
return p
|
||||
}
|
||||
|
||||
// Float64 defines an int flag with specified name, default value, and usage string.
|
||||
// Float64 defines a float64 flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a float64 variable that stores the value of the flag.
|
||||
func Float64(name string, value float64, usage string) *float64 {
|
||||
return commandLine.Float64(name, value, usage)
|
||||
}
|
||||
|
||||
// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
|
||||
// The argument p points to a time.Duration variable in which to store the value of the flag.
|
||||
func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
|
||||
f.Var(newDurationValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
|
||||
// The argument p points to a time.Duration variable in which to store the value of the flag.
|
||||
func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
|
||||
commandLine.Var(newDurationValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// Duration defines a time.Duration flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a time.Duration variable that stores the value of the flag.
|
||||
func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration {
|
||||
p := new(time.Duration)
|
||||
f.DurationVar(p, name, value, usage)
|
||||
return p
|
||||
}
|
||||
|
||||
// Duration defines a time.Duration flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a time.Duration variable that stores the value of the flag.
|
||||
func Duration(name string, value time.Duration, usage string) *time.Duration {
|
||||
return commandLine.Duration(name, value, usage)
|
||||
}
|
||||
|
||||
// Var defines a flag with the specified name and usage string. The type and
|
||||
// value of the flag are represented by the first argument, of type Value, which
|
||||
// typically holds a user-defined implementation of Value. For instance, the
|
||||
|
@ -645,8 +686,8 @@ func (f *FlagSet) parseOne() (bool, error) {
|
|||
}
|
||||
if fv, ok := flag.Value.(*boolValue); ok { // special case: doesn't need an arg
|
||||
if has_value {
|
||||
if !fv.Set(value) {
|
||||
f.failf("invalid boolean value %q for flag: -%s", value, name)
|
||||
if err := fv.Set(value); err != nil {
|
||||
f.failf("invalid boolean value %q for -%s: %v", value, name, err)
|
||||
}
|
||||
} else {
|
||||
fv.Set("true")
|
||||
|
@ -661,9 +702,8 @@ func (f *FlagSet) parseOne() (bool, error) {
|
|||
if !has_value {
|
||||
return false, f.failf("flag needs an argument: -%s", name)
|
||||
}
|
||||
ok = flag.Value.Set(value)
|
||||
if !ok {
|
||||
return false, f.failf("invalid value %q for flag: -%s", value, name)
|
||||
if err := flag.Value.Set(value); err != nil {
|
||||
return false, f.failf("invalid value %q for flag -%s: %v", value, name, err)
|
||||
}
|
||||
}
|
||||
if f.actual == nil {
|
||||
|
|
|
@ -10,16 +10,18 @@ import (
|
|||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
test_bool = Bool("test_bool", false, "bool value")
|
||||
test_int = Int("test_int", 0, "int value")
|
||||
test_int64 = Int64("test_int64", 0, "int64 value")
|
||||
test_uint = Uint("test_uint", 0, "uint value")
|
||||
test_uint64 = Uint64("test_uint64", 0, "uint64 value")
|
||||
test_string = String("test_string", "0", "string value")
|
||||
test_float64 = Float64("test_float64", 0, "float64 value")
|
||||
test_bool = Bool("test_bool", false, "bool value")
|
||||
test_int = Int("test_int", 0, "int value")
|
||||
test_int64 = Int64("test_int64", 0, "int64 value")
|
||||
test_uint = Uint("test_uint", 0, "uint value")
|
||||
test_uint64 = Uint64("test_uint64", 0, "uint64 value")
|
||||
test_string = String("test_string", "0", "string value")
|
||||
test_float64 = Float64("test_float64", 0, "float64 value")
|
||||
test_duration = Duration("test_duration", 0, "time.Duration value")
|
||||
)
|
||||
|
||||
func boolString(s string) string {
|
||||
|
@ -41,6 +43,8 @@ func TestEverything(t *testing.T) {
|
|||
ok = true
|
||||
case f.Name == "test_bool" && f.Value.String() == boolString(desired):
|
||||
ok = true
|
||||
case f.Name == "test_duration" && f.Value.String() == desired+"s":
|
||||
ok = true
|
||||
}
|
||||
if !ok {
|
||||
t.Error("Visit: bad value", f.Value.String(), "for", f.Name)
|
||||
|
@ -48,7 +52,7 @@ func TestEverything(t *testing.T) {
|
|||
}
|
||||
}
|
||||
VisitAll(visitor)
|
||||
if len(m) != 7 {
|
||||
if len(m) != 8 {
|
||||
t.Error("VisitAll misses some flags")
|
||||
for k, v := range m {
|
||||
t.Log(k, *v)
|
||||
|
@ -70,9 +74,10 @@ func TestEverything(t *testing.T) {
|
|||
Set("test_uint64", "1")
|
||||
Set("test_string", "1")
|
||||
Set("test_float64", "1")
|
||||
Set("test_duration", "1s")
|
||||
desired = "1"
|
||||
Visit(visitor)
|
||||
if len(m) != 7 {
|
||||
if len(m) != 8 {
|
||||
t.Error("Visit fails after set")
|
||||
for k, v := range m {
|
||||
t.Log(k, *v)
|
||||
|
@ -109,6 +114,7 @@ func testParse(f *FlagSet, t *testing.T) {
|
|||
uint64Flag := f.Uint64("uint64", 0, "uint64 value")
|
||||
stringFlag := f.String("string", "0", "string value")
|
||||
float64Flag := f.Float64("float64", 0, "float64 value")
|
||||
durationFlag := f.Duration("duration", 5*time.Second, "time.Duration value")
|
||||
extra := "one-extra-argument"
|
||||
args := []string{
|
||||
"-bool",
|
||||
|
@ -119,6 +125,7 @@ func testParse(f *FlagSet, t *testing.T) {
|
|||
"--uint64", "25",
|
||||
"-string", "hello",
|
||||
"-float64", "2718e28",
|
||||
"-duration", "2m",
|
||||
extra,
|
||||
}
|
||||
if err := f.Parse(args); err != nil {
|
||||
|
@ -151,6 +158,9 @@ func testParse(f *FlagSet, t *testing.T) {
|
|||
if *float64Flag != 2718e28 {
|
||||
t.Error("float64 flag should be 2718e28, is ", *float64Flag)
|
||||
}
|
||||
if *durationFlag != 2*time.Minute {
|
||||
t.Error("duration flag should be 2m, is ", *durationFlag)
|
||||
}
|
||||
if len(f.Args()) != 1 {
|
||||
t.Error("expected one argument, got", len(f.Args()))
|
||||
} else if f.Args()[0] != extra {
|
||||
|
@ -174,9 +184,9 @@ func (f *flagVar) String() string {
|
|||
return fmt.Sprint([]string(*f))
|
||||
}
|
||||
|
||||
func (f *flagVar) Set(value string) bool {
|
||||
func (f *flagVar) Set(value string) error {
|
||||
*f = append(*f, value)
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestUserDefined(t *testing.T) {
|
||||
|
|
|
@ -30,8 +30,9 @@
|
|||
%X base 16, with upper-case letters for A-F
|
||||
%U Unicode format: U+1234; same as "U+%04X"
|
||||
Floating-point and complex constituents:
|
||||
%b decimalless scientific notation with exponent a power
|
||||
of two, in the manner of strconv.Ftoa32, e.g. -123456p-78
|
||||
%b decimalless scientific notation with exponent a power of two,
|
||||
in the manner of strconv.FormatFloat with the 'b' format,
|
||||
e.g. -123456p-78
|
||||
%e scientific notation, e.g. -1234.456e+78
|
||||
%E scientific notation, e.g. -1234.456E+78
|
||||
%f decimal point but no exponent, e.g. 123.456
|
||||
|
|
|
@ -517,7 +517,7 @@ var mallocTest = []struct {
|
|||
{1, `Sprintf("xxx")`, func() { Sprintf("xxx") }},
|
||||
{1, `Sprintf("%x")`, func() { Sprintf("%x", 7) }},
|
||||
{2, `Sprintf("%s")`, func() { Sprintf("%s", "hello") }},
|
||||
{1, `Sprintf("%x %x")`, func() { Sprintf("%x", 7, 112) }},
|
||||
{1, `Sprintf("%x %x")`, func() { Sprintf("%x %x", 7, 112) }},
|
||||
{1, `Sprintf("%g")`, func() { Sprintf("%g", 3.14159) }},
|
||||
{0, `Fprintf(buf, "%x %x %x")`, func() { mallocBuf.Reset(); Fprintf(&mallocBuf, "%x %x %x", 7, 8, 9) }},
|
||||
{1, `Fprintf(buf, "%s")`, func() { mallocBuf.Reset(); Fprintf(&mallocBuf, "%s", "hello") }},
|
||||
|
|
|
@ -9,6 +9,7 @@ package ast
|
|||
|
||||
import (
|
||||
"go/token"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
@ -76,6 +77,74 @@ type CommentGroup struct {
|
|||
func (g *CommentGroup) Pos() token.Pos { return g.List[0].Pos() }
|
||||
func (g *CommentGroup) End() token.Pos { return g.List[len(g.List)-1].End() }
|
||||
|
||||
func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' }
|
||||
|
||||
func stripTrailingWhitespace(s string) string {
|
||||
i := len(s)
|
||||
for i > 0 && isWhitespace(s[i-1]) {
|
||||
i--
|
||||
}
|
||||
return s[0:i]
|
||||
}
|
||||
|
||||
// Text returns the text of the comment,
|
||||
// with the comment markers - //, /*, and */ - removed.
|
||||
func (g *CommentGroup) Text() string {
|
||||
if g == nil {
|
||||
return ""
|
||||
}
|
||||
comments := make([]string, len(g.List))
|
||||
for i, c := range g.List {
|
||||
comments[i] = string(c.Text)
|
||||
}
|
||||
|
||||
lines := make([]string, 0, 10) // most comments are less than 10 lines
|
||||
for _, c := range comments {
|
||||
// Remove comment markers.
|
||||
// The parser has given us exactly the comment text.
|
||||
switch c[1] {
|
||||
case '/':
|
||||
//-style comment
|
||||
c = c[2:]
|
||||
// Remove leading space after //, if there is one.
|
||||
// TODO(gri) This appears to be necessary in isolated
|
||||
// cases (bignum.RatFromString) - why?
|
||||
if len(c) > 0 && c[0] == ' ' {
|
||||
c = c[1:]
|
||||
}
|
||||
case '*':
|
||||
/*-style comment */
|
||||
c = c[2 : len(c)-2]
|
||||
}
|
||||
|
||||
// Split on newlines.
|
||||
cl := strings.Split(c, "\n")
|
||||
|
||||
// Walk lines, stripping trailing white space and adding to list.
|
||||
for _, l := range cl {
|
||||
lines = append(lines, stripTrailingWhitespace(l))
|
||||
}
|
||||
}
|
||||
|
||||
// Remove leading blank lines; convert runs of
|
||||
// interior blank lines to a single blank line.
|
||||
n := 0
|
||||
for _, line := range lines {
|
||||
if line != "" || n > 0 && lines[n-1] != "" {
|
||||
lines[n] = line
|
||||
n++
|
||||
}
|
||||
}
|
||||
lines = lines[0:n]
|
||||
|
||||
// Add final "" entry to get trailing newline from Join.
|
||||
if n > 0 && lines[n-1] != "" {
|
||||
lines = append(lines, "")
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Expressions and types
|
||||
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
|
||||
package ast
|
||||
|
||||
import "go/token"
|
||||
import (
|
||||
"go/token"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Export filtering
|
||||
|
@ -20,7 +23,7 @@ func exportFilter(name string) bool {
|
|||
// body) are removed. Non-exported fields and methods of exported types are
|
||||
// stripped. The File.Comments list is not changed.
|
||||
//
|
||||
// FileExports returns true if there are exported declarationa;
|
||||
// FileExports returns true if there are exported declarations;
|
||||
// it returns false otherwise.
|
||||
//
|
||||
func FileExports(src *File) bool {
|
||||
|
@ -291,29 +294,35 @@ var separator = &Comment{noPos, "//"}
|
|||
//
|
||||
func MergePackageFiles(pkg *Package, mode MergeMode) *File {
|
||||
// Count the number of package docs, comments and declarations across
|
||||
// all package files.
|
||||
// all package files. Also, compute sorted list of filenames, so that
|
||||
// subsequent iterations can always iterate in the same order.
|
||||
ndocs := 0
|
||||
ncomments := 0
|
||||
ndecls := 0
|
||||
for _, f := range pkg.Files {
|
||||
filenames := make([]string, len(pkg.Files))
|
||||
i := 0
|
||||
for filename, f := range pkg.Files {
|
||||
filenames[i] = filename
|
||||
i++
|
||||
if f.Doc != nil {
|
||||
ndocs += len(f.Doc.List) + 1 // +1 for separator
|
||||
}
|
||||
ncomments += len(f.Comments)
|
||||
ndecls += len(f.Decls)
|
||||
}
|
||||
sort.Strings(filenames)
|
||||
|
||||
// Collect package comments from all package files into a single
|
||||
// CommentGroup - the collected package documentation. The order
|
||||
// is unspecified. In general there should be only one file with
|
||||
// a package comment; but it's better to collect extra comments
|
||||
// than drop them on the floor.
|
||||
// CommentGroup - the collected package documentation. In general
|
||||
// there should be only one file with a package comment; but it's
|
||||
// better to collect extra comments than drop them on the floor.
|
||||
var doc *CommentGroup
|
||||
var pos token.Pos
|
||||
if ndocs > 0 {
|
||||
list := make([]*Comment, ndocs-1) // -1: no separator before first group
|
||||
i := 0
|
||||
for _, f := range pkg.Files {
|
||||
for _, filename := range filenames {
|
||||
f := pkg.Files[filename]
|
||||
if f.Doc != nil {
|
||||
if i > 0 {
|
||||
// not the first group - add separator
|
||||
|
@ -342,7 +351,8 @@ func MergePackageFiles(pkg *Package, mode MergeMode) *File {
|
|||
funcs := make(map[string]int) // map of global function name -> decls index
|
||||
i := 0 // current index
|
||||
n := 0 // number of filtered entries
|
||||
for _, f := range pkg.Files {
|
||||
for _, filename := range filenames {
|
||||
f := pkg.Files[filename]
|
||||
for _, d := range f.Decls {
|
||||
if mode&FilterFuncDuplicates != 0 {
|
||||
// A language entity may be declared multiple
|
||||
|
@ -398,7 +408,8 @@ func MergePackageFiles(pkg *Package, mode MergeMode) *File {
|
|||
var imports []*ImportSpec
|
||||
if mode&FilterImportDuplicates != 0 {
|
||||
seen := make(map[string]bool)
|
||||
for _, f := range pkg.Files {
|
||||
for _, filename := range filenames {
|
||||
f := pkg.Files[filename]
|
||||
for _, imp := range f.Imports {
|
||||
if path := imp.Path.Value; !seen[path] {
|
||||
// TODO: consider handling cases where:
|
||||
|
|
|
@ -36,7 +36,7 @@ func NotNilFilter(_ string, v reflect.Value) bool {
|
|||
// struct fields for which f(fieldname, fieldvalue) is true are
|
||||
// are printed; all others are filtered from the output.
|
||||
//
|
||||
func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (n int, err error) {
|
||||
func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (err error) {
|
||||
// setup printer
|
||||
p := printer{
|
||||
output: w,
|
||||
|
@ -48,7 +48,6 @@ func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (n i
|
|||
|
||||
// install error handler
|
||||
defer func() {
|
||||
n = p.written
|
||||
if e := recover(); e != nil {
|
||||
err = e.(localError).err // re-panics if it's not a localError
|
||||
}
|
||||
|
@ -67,19 +66,18 @@ func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (n i
|
|||
|
||||
// Print prints x to standard output, skipping nil fields.
|
||||
// Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter).
|
||||
func Print(fset *token.FileSet, x interface{}) (int, error) {
|
||||
func Print(fset *token.FileSet, x interface{}) error {
|
||||
return Fprint(os.Stdout, fset, x, NotNilFilter)
|
||||
}
|
||||
|
||||
type printer struct {
|
||||
output io.Writer
|
||||
fset *token.FileSet
|
||||
filter FieldFilter
|
||||
ptrmap map[interface{}]int // *T -> line number
|
||||
written int // number of bytes written to output
|
||||
indent int // current indentation level
|
||||
last byte // the last byte processed by Write
|
||||
line int // current line number
|
||||
output io.Writer
|
||||
fset *token.FileSet
|
||||
filter FieldFilter
|
||||
ptrmap map[interface{}]int // *T -> line number
|
||||
indent int // current indentation level
|
||||
last byte // the last byte processed by Write
|
||||
line int // current line number
|
||||
}
|
||||
|
||||
var indent = []byte(". ")
|
||||
|
@ -122,9 +120,7 @@ type localError struct {
|
|||
|
||||
// printf is a convenience wrapper that takes care of print errors.
|
||||
func (p *printer) printf(format string, args ...interface{}) {
|
||||
n, err := fmt.Fprintf(p, format, args...)
|
||||
p.written += n
|
||||
if err != nil {
|
||||
if _, err := fmt.Fprintf(p, format, args...); err != nil {
|
||||
panic(localError{err})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ func TestPrint(t *testing.T) {
|
|||
var buf bytes.Buffer
|
||||
for _, test := range tests {
|
||||
buf.Reset()
|
||||
if _, err := Fprint(&buf, nil, test.x, nil); err != nil {
|
||||
if err := Fprint(&buf, nil, test.x, nil); err != nil {
|
||||
t.Errorf("Fprint failed: %s", err)
|
||||
}
|
||||
if s, ts := trim(buf.String()), trim(test.s); s != ts {
|
||||
|
|
|
@ -80,7 +80,7 @@ func (s *Scope) String() string {
|
|||
type Object struct {
|
||||
Kind ObjKind
|
||||
Name string // declared name
|
||||
Decl interface{} // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, or AssignStmt; or nil
|
||||
Decl interface{} // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, AssignStmt, Scope; or nil
|
||||
Data interface{} // object-specific data; or nil
|
||||
Type interface{} // place holder for type information; may be nil
|
||||
}
|
||||
|
@ -131,6 +131,8 @@ func (obj *Object) Pos() token.Pos {
|
|||
return ident.Pos()
|
||||
}
|
||||
}
|
||||
case *Scope:
|
||||
// predeclared object - nothing to do for now
|
||||
}
|
||||
return token.NoPos
|
||||
}
|
||||
|
|
|
@ -396,8 +396,7 @@ func (b *build) cgo(cgofiles, cgocfiles []string) (outGo, outObj []string) {
|
|||
Output: output,
|
||||
})
|
||||
outGo = append(outGo, gofiles...)
|
||||
exportH := filepath.Join(b.path, "_cgo_export.h")
|
||||
b.script.addIntermediate(defunC, exportH, b.obj+"_cgo_flags")
|
||||
b.script.addIntermediate(defunC, b.obj+"_cgo_export.h", b.obj+"_cgo_flags")
|
||||
b.script.addIntermediate(cfiles...)
|
||||
|
||||
// cc _cgo_defun.c
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/doc"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
|
@ -412,7 +411,7 @@ func (ctxt *Context) shouldBuild(content []byte) bool {
|
|||
// TODO(rsc): This duplicates code in cgo.
|
||||
// Once the dust settles, remove this code from cgo.
|
||||
func (ctxt *Context) saveCgo(filename string, di *DirInfo, cg *ast.CommentGroup) error {
|
||||
text := doc.CommentText(cg)
|
||||
text := cg.Text()
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
orig := line
|
||||
|
||||
|
@ -476,7 +475,7 @@ func (ctxt *Context) saveCgo(filename string, di *DirInfo, cg *ast.CommentGroup)
|
|||
return nil
|
||||
}
|
||||
|
||||
var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz")
|
||||
var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:")
|
||||
|
||||
func safeName(s string) bool {
|
||||
if s == "" {
|
||||
|
|
|
@ -157,6 +157,7 @@ func init() {
|
|||
Path = []*Tree{t}
|
||||
}
|
||||
|
||||
Loop:
|
||||
for _, p := range filepath.SplitList(os.Getenv("GOPATH")) {
|
||||
if p == "" {
|
||||
continue
|
||||
|
@ -166,6 +167,21 @@ func init() {
|
|||
log.Printf("invalid GOPATH %q: %v", p, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for dupes.
|
||||
// TODO(alexbrainman): make this correct under windows (case insensitive).
|
||||
for _, t2 := range Path {
|
||||
if t2.Path != t.Path {
|
||||
continue
|
||||
}
|
||||
if t2.Goroot {
|
||||
log.Printf("GOPATH is the same as GOROOT: %q", t.Path)
|
||||
} else {
|
||||
log.Printf("duplicate GOPATH entry: %q", t.Path)
|
||||
}
|
||||
continue Loop
|
||||
}
|
||||
|
||||
Path = append(Path, t)
|
||||
gcImportArgs = append(gcImportArgs, "-I", t.PkgDir())
|
||||
ldImportArgs = append(ldImportArgs, "-L", t.PkgDir())
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
package doc
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
@ -16,74 +15,6 @@ import (
|
|||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' }
|
||||
|
||||
func stripTrailingWhitespace(s string) string {
|
||||
i := len(s)
|
||||
for i > 0 && isWhitespace(s[i-1]) {
|
||||
i--
|
||||
}
|
||||
return s[0:i]
|
||||
}
|
||||
|
||||
// CommentText returns the text of comment,
|
||||
// with the comment markers - //, /*, and */ - removed.
|
||||
func CommentText(comment *ast.CommentGroup) string {
|
||||
if comment == nil {
|
||||
return ""
|
||||
}
|
||||
comments := make([]string, len(comment.List))
|
||||
for i, c := range comment.List {
|
||||
comments[i] = string(c.Text)
|
||||
}
|
||||
|
||||
lines := make([]string, 0, 10) // most comments are less than 10 lines
|
||||
for _, c := range comments {
|
||||
// Remove comment markers.
|
||||
// The parser has given us exactly the comment text.
|
||||
switch c[1] {
|
||||
case '/':
|
||||
//-style comment
|
||||
c = c[2:]
|
||||
// Remove leading space after //, if there is one.
|
||||
// TODO(gri) This appears to be necessary in isolated
|
||||
// cases (bignum.RatFromString) - why?
|
||||
if len(c) > 0 && c[0] == ' ' {
|
||||
c = c[1:]
|
||||
}
|
||||
case '*':
|
||||
/*-style comment */
|
||||
c = c[2 : len(c)-2]
|
||||
}
|
||||
|
||||
// Split on newlines.
|
||||
cl := strings.Split(c, "\n")
|
||||
|
||||
// Walk lines, stripping trailing white space and adding to list.
|
||||
for _, l := range cl {
|
||||
lines = append(lines, stripTrailingWhitespace(l))
|
||||
}
|
||||
}
|
||||
|
||||
// Remove leading blank lines; convert runs of
|
||||
// interior blank lines to a single blank line.
|
||||
n := 0
|
||||
for _, line := range lines {
|
||||
if line != "" || n > 0 && lines[n-1] != "" {
|
||||
lines[n] = line
|
||||
n++
|
||||
}
|
||||
}
|
||||
lines = lines[0:n]
|
||||
|
||||
// Add final "" entry to get trailing newline from Join.
|
||||
if n > 0 && lines[n-1] != "" {
|
||||
lines = append(lines, "")
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
var (
|
||||
ldquo = []byte("“")
|
||||
rdquo = []byte("”")
|
||||
|
@ -422,12 +353,10 @@ func ToText(w io.Writer, text string, indent, preIndent string, width int) {
|
|||
width: width,
|
||||
indent: indent,
|
||||
}
|
||||
for i, b := range blocks(text) {
|
||||
for _, b := range blocks(text) {
|
||||
switch b.op {
|
||||
case opPara:
|
||||
if i > 0 {
|
||||
w.Write(nl)
|
||||
}
|
||||
// l.write will add leading newline if required
|
||||
for _, line := range b.lines {
|
||||
l.write(line)
|
||||
}
|
||||
|
|
|
@ -7,673 +7,94 @@ package doc
|
|||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"regexp"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Collection of documentation info
|
||||
|
||||
// embeddedType describes the type of an anonymous field.
|
||||
//
|
||||
type embeddedType struct {
|
||||
typ *typeInfo // the corresponding base type
|
||||
ptr bool // if set, the anonymous field type is a pointer
|
||||
// Package is the documentation for an entire package.
|
||||
type Package struct {
|
||||
Doc string
|
||||
Name string
|
||||
ImportPath string
|
||||
Imports []string // TODO(gri) this field is not computed at the moment
|
||||
Filenames []string
|
||||
Consts []*Value
|
||||
Types []*Type
|
||||
Vars []*Value
|
||||
Funcs []*Func
|
||||
Bugs []string
|
||||
}
|
||||
|
||||
type typeInfo struct {
|
||||
// len(decl.Specs) == 1, and the element type is *ast.TypeSpec
|
||||
// if the type declaration hasn't been seen yet, decl is nil
|
||||
decl *ast.GenDecl
|
||||
embedded []embeddedType
|
||||
forward *TypeDoc // forward link to processed type documentation
|
||||
|
||||
// declarations associated with the type
|
||||
values []*ast.GenDecl // consts and vars
|
||||
factories map[string]*ast.FuncDecl
|
||||
methods map[string]*ast.FuncDecl
|
||||
}
|
||||
|
||||
func (info *typeInfo) addEmbeddedType(embedded *typeInfo, isPtr bool) {
|
||||
info.embedded = append(info.embedded, embeddedType{embedded, isPtr})
|
||||
}
|
||||
|
||||
// docReader accumulates documentation for a single package.
|
||||
// It modifies the AST: Comments (declaration documentation)
|
||||
// that have been collected by the DocReader are set to nil
|
||||
// in the respective AST nodes so that they are not printed
|
||||
// twice (once when printing the documentation and once when
|
||||
// printing the corresponding AST node).
|
||||
//
|
||||
type docReader struct {
|
||||
doc *ast.CommentGroup // package documentation, if any
|
||||
pkgName string
|
||||
values []*ast.GenDecl // consts and vars
|
||||
types map[string]*typeInfo
|
||||
embedded map[string]*typeInfo // embedded types, possibly not exported
|
||||
funcs map[string]*ast.FuncDecl
|
||||
bugs []*ast.CommentGroup
|
||||
}
|
||||
|
||||
func (doc *docReader) init(pkgName string) {
|
||||
doc.pkgName = pkgName
|
||||
doc.types = make(map[string]*typeInfo)
|
||||
doc.embedded = make(map[string]*typeInfo)
|
||||
doc.funcs = make(map[string]*ast.FuncDecl)
|
||||
}
|
||||
|
||||
func (doc *docReader) addDoc(comments *ast.CommentGroup) {
|
||||
if doc.doc == nil {
|
||||
// common case: just one package comment
|
||||
doc.doc = comments
|
||||
return
|
||||
}
|
||||
// More than one package comment: Usually there will be only
|
||||
// one file with a package comment, but it's better to collect
|
||||
// all comments than drop them on the floor.
|
||||
blankComment := &ast.Comment{token.NoPos, "//"}
|
||||
list := append(doc.doc.List, blankComment)
|
||||
doc.doc.List = append(list, comments.List...)
|
||||
}
|
||||
|
||||
func (doc *docReader) lookupTypeInfo(name string) *typeInfo {
|
||||
if name == "" || name == "_" {
|
||||
return nil // no type docs for anonymous types
|
||||
}
|
||||
if info, found := doc.types[name]; found {
|
||||
return info
|
||||
}
|
||||
// type wasn't found - add one without declaration
|
||||
info := &typeInfo{
|
||||
factories: make(map[string]*ast.FuncDecl),
|
||||
methods: make(map[string]*ast.FuncDecl),
|
||||
}
|
||||
doc.types[name] = info
|
||||
return info
|
||||
}
|
||||
|
||||
func baseTypeName(typ ast.Expr, allTypes bool) string {
|
||||
switch t := typ.(type) {
|
||||
case *ast.Ident:
|
||||
// if the type is not exported, the effect to
|
||||
// a client is as if there were no type name
|
||||
if t.IsExported() || allTypes {
|
||||
return t.Name
|
||||
}
|
||||
case *ast.StarExpr:
|
||||
return baseTypeName(t.X, allTypes)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (doc *docReader) addValue(decl *ast.GenDecl) {
|
||||
// determine if decl should be associated with a type
|
||||
// Heuristic: For each typed entry, determine the type name, if any.
|
||||
// If there is exactly one type name that is sufficiently
|
||||
// frequent, associate the decl with the respective type.
|
||||
domName := ""
|
||||
domFreq := 0
|
||||
prev := ""
|
||||
for _, s := range decl.Specs {
|
||||
if v, ok := s.(*ast.ValueSpec); ok {
|
||||
name := ""
|
||||
switch {
|
||||
case v.Type != nil:
|
||||
// a type is present; determine its name
|
||||
name = baseTypeName(v.Type, false)
|
||||
case decl.Tok == token.CONST:
|
||||
// no type is present but we have a constant declaration;
|
||||
// use the previous type name (w/o more type information
|
||||
// we cannot handle the case of unnamed variables with
|
||||
// initializer expressions except for some trivial cases)
|
||||
name = prev
|
||||
}
|
||||
if name != "" {
|
||||
// entry has a named type
|
||||
if domName != "" && domName != name {
|
||||
// more than one type name - do not associate
|
||||
// with any type
|
||||
domName = ""
|
||||
break
|
||||
}
|
||||
domName = name
|
||||
domFreq++
|
||||
}
|
||||
prev = name
|
||||
}
|
||||
}
|
||||
|
||||
// determine values list
|
||||
const threshold = 0.75
|
||||
values := &doc.values
|
||||
if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) {
|
||||
// typed entries are sufficiently frequent
|
||||
typ := doc.lookupTypeInfo(domName)
|
||||
if typ != nil {
|
||||
values = &typ.values // associate with that type
|
||||
}
|
||||
}
|
||||
|
||||
*values = append(*values, decl)
|
||||
}
|
||||
|
||||
// Helper function to set the table entry for function f. Makes sure that
|
||||
// at least one f with associated documentation is stored in table, if there
|
||||
// are multiple f's with the same name.
|
||||
func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) {
|
||||
name := f.Name.Name
|
||||
if g, exists := table[name]; exists && g.Doc != nil {
|
||||
// a function with the same name has already been registered;
|
||||
// since it has documentation, assume f is simply another
|
||||
// implementation and ignore it
|
||||
// TODO(gri) consider collecting all functions, or at least
|
||||
// all comments
|
||||
return
|
||||
}
|
||||
// function doesn't exist or has no documentation; use f
|
||||
table[name] = f
|
||||
}
|
||||
|
||||
func (doc *docReader) addFunc(fun *ast.FuncDecl) {
|
||||
// strip function body
|
||||
fun.Body = nil
|
||||
|
||||
// determine if it should be associated with a type
|
||||
if fun.Recv != nil {
|
||||
// method
|
||||
typ := doc.lookupTypeInfo(baseTypeName(fun.Recv.List[0].Type, false))
|
||||
if typ != nil {
|
||||
// exported receiver type
|
||||
setFunc(typ.methods, fun)
|
||||
}
|
||||
// otherwise don't show the method
|
||||
// TODO(gri): There may be exported methods of non-exported types
|
||||
// that can be called because of exported values (consts, vars, or
|
||||
// function results) of that type. Could determine if that is the
|
||||
// case and then show those methods in an appropriate section.
|
||||
return
|
||||
}
|
||||
|
||||
// perhaps a factory function
|
||||
// determine result type, if any
|
||||
if fun.Type.Results.NumFields() >= 1 {
|
||||
res := fun.Type.Results.List[0]
|
||||
if len(res.Names) <= 1 {
|
||||
// exactly one (named or anonymous) result associated
|
||||
// with the first type in result signature (there may
|
||||
// be more than one result)
|
||||
tname := baseTypeName(res.Type, false)
|
||||
typ := doc.lookupTypeInfo(tname)
|
||||
if typ != nil {
|
||||
// named and exported result type
|
||||
setFunc(typ.factories, fun)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ordinary function
|
||||
setFunc(doc.funcs, fun)
|
||||
}
|
||||
|
||||
func (doc *docReader) addDecl(decl ast.Decl) {
|
||||
switch d := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
if len(d.Specs) > 0 {
|
||||
switch d.Tok {
|
||||
case token.CONST, token.VAR:
|
||||
// constants and variables are always handled as a group
|
||||
doc.addValue(d)
|
||||
case token.TYPE:
|
||||
// types are handled individually
|
||||
for _, spec := range d.Specs {
|
||||
tspec := spec.(*ast.TypeSpec)
|
||||
// add the type to the documentation
|
||||
info := doc.lookupTypeInfo(tspec.Name.Name)
|
||||
if info == nil {
|
||||
continue // no name - ignore the type
|
||||
}
|
||||
// Make a (fake) GenDecl node for this TypeSpec
|
||||
// (we need to do this here - as opposed to just
|
||||
// for printing - so we don't lose the GenDecl
|
||||
// documentation). Since a new GenDecl node is
|
||||
// created, there's no need to nil out d.Doc.
|
||||
//
|
||||
// TODO(gri): Consider just collecting the TypeSpec
|
||||
// node (and copy in the GenDecl.doc if there is no
|
||||
// doc in the TypeSpec - this is currently done in
|
||||
// makeTypeDocs below). Simpler data structures, but
|
||||
// would lose GenDecl documentation if the TypeSpec
|
||||
// has documentation as well.
|
||||
fake := &ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos,
|
||||
[]ast.Spec{tspec}, token.NoPos}
|
||||
// A type should be added at most once, so info.decl
|
||||
// should be nil - if it isn't, simply overwrite it.
|
||||
info.decl = fake
|
||||
// Look for anonymous fields that might contribute methods.
|
||||
var fields *ast.FieldList
|
||||
switch typ := spec.(*ast.TypeSpec).Type.(type) {
|
||||
case *ast.StructType:
|
||||
fields = typ.Fields
|
||||
case *ast.InterfaceType:
|
||||
fields = typ.Methods
|
||||
}
|
||||
if fields != nil {
|
||||
for _, field := range fields.List {
|
||||
if len(field.Names) == 0 {
|
||||
// anonymous field - add corresponding type
|
||||
// to the info and collect it in doc
|
||||
name := baseTypeName(field.Type, true)
|
||||
if embedded := doc.lookupTypeInfo(name); embedded != nil {
|
||||
_, ptr := field.Type.(*ast.StarExpr)
|
||||
info.addEmbeddedType(embedded, ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
doc.addFunc(d)
|
||||
}
|
||||
}
|
||||
|
||||
func copyCommentList(list []*ast.Comment) []*ast.Comment {
|
||||
return append([]*ast.Comment(nil), list...)
|
||||
}
|
||||
|
||||
var (
|
||||
bug_markers = regexp.MustCompile("^/[/*][ \t]*BUG\\(.*\\):[ \t]*") // BUG(uid):
|
||||
bug_content = regexp.MustCompile("[^ \n\r\t]+") // at least one non-whitespace char
|
||||
)
|
||||
|
||||
// addFile adds the AST for a source file to the docReader.
|
||||
// Adding the same AST multiple times is a no-op.
|
||||
//
|
||||
func (doc *docReader) addFile(src *ast.File) {
|
||||
// add package documentation
|
||||
if src.Doc != nil {
|
||||
doc.addDoc(src.Doc)
|
||||
src.Doc = nil // doc consumed - remove from ast.File node
|
||||
}
|
||||
|
||||
// add all declarations
|
||||
for _, decl := range src.Decls {
|
||||
doc.addDecl(decl)
|
||||
}
|
||||
|
||||
// collect BUG(...) comments
|
||||
for _, c := range src.Comments {
|
||||
text := c.List[0].Text
|
||||
if m := bug_markers.FindStringIndex(text); m != nil {
|
||||
// found a BUG comment; maybe empty
|
||||
if btxt := text[m[1]:]; bug_content.MatchString(btxt) {
|
||||
// non-empty BUG comment; collect comment without BUG prefix
|
||||
list := copyCommentList(c.List)
|
||||
list[0].Text = text[m[1]:]
|
||||
doc.bugs = append(doc.bugs, &ast.CommentGroup{list})
|
||||
}
|
||||
}
|
||||
}
|
||||
src.Comments = nil // consumed unassociated comments - remove from ast.File node
|
||||
}
|
||||
|
||||
func NewPackageDoc(pkg *ast.Package, importpath string, exportsOnly bool) *PackageDoc {
|
||||
var r docReader
|
||||
r.init(pkg.Name)
|
||||
filenames := make([]string, len(pkg.Files))
|
||||
i := 0
|
||||
for filename, f := range pkg.Files {
|
||||
if exportsOnly {
|
||||
r.fileExports(f)
|
||||
}
|
||||
r.addFile(f)
|
||||
filenames[i] = filename
|
||||
i++
|
||||
}
|
||||
return r.newDoc(importpath, filenames)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Conversion to external representation
|
||||
|
||||
// ValueDoc is the documentation for a group of declared
|
||||
// values, either vars or consts.
|
||||
//
|
||||
type ValueDoc struct {
|
||||
// Value is the documentation for a (possibly grouped) var or const declaration.
|
||||
type Value struct {
|
||||
Doc string
|
||||
Names []string // var or const names in declaration order
|
||||
Decl *ast.GenDecl
|
||||
|
||||
order int
|
||||
}
|
||||
|
||||
type sortValueDoc []*ValueDoc
|
||||
|
||||
func (p sortValueDoc) Len() int { return len(p) }
|
||||
func (p sortValueDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
func declName(d *ast.GenDecl) string {
|
||||
if len(d.Specs) != 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch v := d.Specs[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
return v.Names[0].Name
|
||||
case *ast.TypeSpec:
|
||||
return v.Name.Name
|
||||
}
|
||||
|
||||
return ""
|
||||
type Method struct {
|
||||
*Func
|
||||
// TODO(gri) The following fields are not set at the moment.
|
||||
Recv *Type // original receiver base type
|
||||
Level int // embedding level; 0 means Func is not embedded
|
||||
}
|
||||
|
||||
func (p sortValueDoc) Less(i, j int) bool {
|
||||
// sort by name
|
||||
// pull blocks (name = "") up to top
|
||||
// in original order
|
||||
if ni, nj := declName(p[i].Decl), declName(p[j].Decl); ni != nj {
|
||||
return ni < nj
|
||||
}
|
||||
return p[i].order < p[j].order
|
||||
// Type is the documentation for type declaration.
|
||||
type Type struct {
|
||||
Doc string
|
||||
Name string
|
||||
Type *ast.TypeSpec
|
||||
Decl *ast.GenDecl
|
||||
Consts []*Value // sorted list of constants of (mostly) this type
|
||||
Vars []*Value // sorted list of variables of (mostly) this type
|
||||
Funcs []*Func // sorted list of functions returning this type
|
||||
Methods []*Method // sorted list of methods (including embedded ones) of this type
|
||||
|
||||
methods []*Func // top-level methods only
|
||||
embedded methodSet // embedded methods only
|
||||
order int
|
||||
}
|
||||
|
||||
func makeValueDocs(list []*ast.GenDecl, tok token.Token) []*ValueDoc {
|
||||
d := make([]*ValueDoc, len(list)) // big enough in any case
|
||||
n := 0
|
||||
for i, decl := range list {
|
||||
if decl.Tok == tok {
|
||||
d[n] = &ValueDoc{CommentText(decl.Doc), decl, i}
|
||||
n++
|
||||
decl.Doc = nil // doc consumed - removed from AST
|
||||
}
|
||||
}
|
||||
d = d[0:n]
|
||||
sort.Sort(sortValueDoc(d))
|
||||
return d
|
||||
}
|
||||
|
||||
// FuncDoc is the documentation for a func declaration,
|
||||
// either a top-level function or a method function.
|
||||
//
|
||||
type FuncDoc struct {
|
||||
// Func is the documentation for a func declaration.
|
||||
type Func struct {
|
||||
Doc string
|
||||
Recv ast.Expr // TODO(rsc): Would like string here
|
||||
Name string
|
||||
// TODO(gri) remove Recv once we switch to new implementation
|
||||
Recv ast.Expr // TODO(rsc): Would like string here
|
||||
Decl *ast.FuncDecl
|
||||
}
|
||||
|
||||
type sortFuncDoc []*FuncDoc
|
||||
// Mode values control the operation of New.
|
||||
type Mode int
|
||||
|
||||
func (p sortFuncDoc) Len() int { return len(p) }
|
||||
func (p sortFuncDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p sortFuncDoc) Less(i, j int) bool { return p[i].Name < p[j].Name }
|
||||
const (
|
||||
// extract documentation for all package-level declarations,
|
||||
// not just exported ones
|
||||
AllDecls Mode = 1 << iota
|
||||
)
|
||||
|
||||
func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc {
|
||||
d := make([]*FuncDoc, len(m))
|
||||
// New computes the package documentation for the given package.
|
||||
func New(pkg *ast.Package, importpath string, mode Mode) *Package {
|
||||
var r docReader
|
||||
r.init(pkg.Name, mode)
|
||||
filenames := make([]string, len(pkg.Files))
|
||||
// sort package files before reading them so that the
|
||||
// result is the same on different machines (32/64bit)
|
||||
i := 0
|
||||
for _, f := range m {
|
||||
doc := new(FuncDoc)
|
||||
doc.Doc = CommentText(f.Doc)
|
||||
f.Doc = nil // doc consumed - remove from ast.FuncDecl node
|
||||
if f.Recv != nil {
|
||||
doc.Recv = f.Recv.List[0].Type
|
||||
}
|
||||
doc.Name = f.Name.Name
|
||||
doc.Decl = f
|
||||
d[i] = doc
|
||||
for filename := range pkg.Files {
|
||||
filenames[i] = filename
|
||||
i++
|
||||
}
|
||||
sort.Sort(sortFuncDoc(d))
|
||||
return d
|
||||
}
|
||||
|
||||
type methodSet map[string]*FuncDoc
|
||||
|
||||
func (mset methodSet) add(m *FuncDoc) {
|
||||
if mset[m.Name] == nil {
|
||||
mset[m.Name] = m
|
||||
}
|
||||
}
|
||||
|
||||
func (mset methodSet) sortedList() []*FuncDoc {
|
||||
list := make([]*FuncDoc, len(mset))
|
||||
i := 0
|
||||
for _, m := range mset {
|
||||
list[i] = m
|
||||
i++
|
||||
}
|
||||
sort.Sort(sortFuncDoc(list))
|
||||
return list
|
||||
}
|
||||
|
||||
// TypeDoc is the documentation for a declared type.
|
||||
// Consts and Vars are sorted lists of constants and variables of (mostly) that type.
|
||||
// Factories is a sorted list of factory functions that return that type.
|
||||
// Methods is a sorted list of method functions on that type.
|
||||
type TypeDoc struct {
|
||||
Doc string
|
||||
Type *ast.TypeSpec
|
||||
Consts []*ValueDoc
|
||||
Vars []*ValueDoc
|
||||
Factories []*FuncDoc
|
||||
methods []*FuncDoc // top-level methods only
|
||||
embedded methodSet // embedded methods only
|
||||
Methods []*FuncDoc // all methods including embedded ones
|
||||
Decl *ast.GenDecl
|
||||
order int
|
||||
}
|
||||
|
||||
type sortTypeDoc []*TypeDoc
|
||||
|
||||
func (p sortTypeDoc) Len() int { return len(p) }
|
||||
func (p sortTypeDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p sortTypeDoc) Less(i, j int) bool {
|
||||
// sort by name
|
||||
// pull blocks (name = "") up to top
|
||||
// in original order
|
||||
if ni, nj := p[i].Type.Name.Name, p[j].Type.Name.Name; ni != nj {
|
||||
return ni < nj
|
||||
}
|
||||
return p[i].order < p[j].order
|
||||
}
|
||||
|
||||
// NOTE(rsc): This would appear not to be correct for type ( )
|
||||
// blocks, but the doc extractor above has split them into
|
||||
// individual declarations.
|
||||
func (doc *docReader) makeTypeDocs(m map[string]*typeInfo) []*TypeDoc {
|
||||
// TODO(gri) Consider computing the embedded method information
|
||||
// before calling makeTypeDocs. Then this function can
|
||||
// be single-phased again. Also, it might simplify some
|
||||
// of the logic.
|
||||
//
|
||||
// phase 1: associate collected declarations with TypeDocs
|
||||
list := make([]*TypeDoc, len(m))
|
||||
i := 0
|
||||
for _, old := range m {
|
||||
// all typeInfos should have a declaration associated with
|
||||
// them after processing an entire package - be conservative
|
||||
// and check
|
||||
if decl := old.decl; decl != nil {
|
||||
typespec := decl.Specs[0].(*ast.TypeSpec)
|
||||
t := new(TypeDoc)
|
||||
doc := typespec.Doc
|
||||
typespec.Doc = nil // doc consumed - remove from ast.TypeSpec node
|
||||
if doc == nil {
|
||||
// no doc associated with the spec, use the declaration doc, if any
|
||||
doc = decl.Doc
|
||||
}
|
||||
decl.Doc = nil // doc consumed - remove from ast.Decl node
|
||||
t.Doc = CommentText(doc)
|
||||
t.Type = typespec
|
||||
t.Consts = makeValueDocs(old.values, token.CONST)
|
||||
t.Vars = makeValueDocs(old.values, token.VAR)
|
||||
t.Factories = makeFuncDocs(old.factories)
|
||||
t.methods = makeFuncDocs(old.methods)
|
||||
// The list of embedded types' methods is computed from the list
|
||||
// of embedded types, some of which may not have been processed
|
||||
// yet (i.e., their forward link is nil) - do this in a 2nd phase.
|
||||
// The final list of methods can only be computed after that -
|
||||
// do this in a 3rd phase.
|
||||
t.Decl = old.decl
|
||||
t.order = i
|
||||
old.forward = t // old has been processed
|
||||
list[i] = t
|
||||
i++
|
||||
} else {
|
||||
// no corresponding type declaration found - move any associated
|
||||
// values, factory functions, and methods back to the top-level
|
||||
// so that they are not lost (this should only happen if a package
|
||||
// file containing the explicit type declaration is missing or if
|
||||
// an unqualified type name was used after a "." import)
|
||||
// 1) move values
|
||||
doc.values = append(doc.values, old.values...)
|
||||
// 2) move factory functions
|
||||
for name, f := range old.factories {
|
||||
doc.funcs[name] = f
|
||||
}
|
||||
// 3) move methods
|
||||
for name, f := range old.methods {
|
||||
// don't overwrite functions with the same name
|
||||
if _, found := doc.funcs[name]; !found {
|
||||
doc.funcs[name] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
list = list[0:i] // some types may have been ignored
|
||||
|
||||
// phase 2: collect embedded methods for each processed typeInfo
|
||||
for _, old := range m {
|
||||
if t := old.forward; t != nil {
|
||||
// old has been processed into t; collect embedded
|
||||
// methods for t from the list of processed embedded
|
||||
// types in old (and thus for which the methods are known)
|
||||
typ := t.Type
|
||||
if _, ok := typ.Type.(*ast.StructType); ok {
|
||||
// struct
|
||||
t.embedded = make(methodSet)
|
||||
collectEmbeddedMethods(t.embedded, old, typ.Name.Name)
|
||||
} else {
|
||||
// interface
|
||||
// TODO(gri) fix this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// phase 3: compute final method set for each TypeDoc
|
||||
for _, d := range list {
|
||||
if len(d.embedded) > 0 {
|
||||
// there are embedded methods - exclude
|
||||
// the ones with names conflicting with
|
||||
// non-embedded methods
|
||||
mset := make(methodSet)
|
||||
// top-level methods have priority
|
||||
for _, m := range d.methods {
|
||||
mset.add(m)
|
||||
}
|
||||
// add non-conflicting embedded methods
|
||||
for _, m := range d.embedded {
|
||||
mset.add(m)
|
||||
}
|
||||
d.Methods = mset.sortedList()
|
||||
} else {
|
||||
// no embedded methods
|
||||
d.Methods = d.methods
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(sortTypeDoc(list))
|
||||
return list
|
||||
}
|
||||
|
||||
// collectEmbeddedMethods collects the embedded methods from all
|
||||
// processed embedded types found in info in mset. It considers
|
||||
// embedded types at the most shallow level first so that more
|
||||
// deeply nested embedded methods with conflicting names are
|
||||
// excluded.
|
||||
//
|
||||
func collectEmbeddedMethods(mset methodSet, info *typeInfo, recvTypeName string) {
|
||||
for _, e := range info.embedded {
|
||||
if e.typ.forward != nil { // == e was processed
|
||||
for _, m := range e.typ.forward.methods {
|
||||
mset.add(customizeRecv(m, e.ptr, recvTypeName))
|
||||
}
|
||||
collectEmbeddedMethods(mset, e.typ, recvTypeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func customizeRecv(m *FuncDoc, embeddedIsPtr bool, recvTypeName string) *FuncDoc {
|
||||
if m == nil || m.Decl == nil || m.Decl.Recv == nil || len(m.Decl.Recv.List) != 1 {
|
||||
return m // shouldn't happen, but be safe
|
||||
}
|
||||
|
||||
// copy existing receiver field and set new type
|
||||
// TODO(gri) is receiver type computation correct?
|
||||
// what about deeply nested embeddings?
|
||||
newField := *m.Decl.Recv.List[0]
|
||||
_, origRecvIsPtr := newField.Type.(*ast.StarExpr)
|
||||
var typ ast.Expr = ast.NewIdent(recvTypeName)
|
||||
if embeddedIsPtr || origRecvIsPtr {
|
||||
typ = &ast.StarExpr{token.NoPos, typ}
|
||||
}
|
||||
newField.Type = typ
|
||||
|
||||
// copy existing receiver field list and set new receiver field
|
||||
newFieldList := *m.Decl.Recv
|
||||
newFieldList.List = []*ast.Field{&newField}
|
||||
|
||||
// copy existing function declaration and set new receiver field list
|
||||
newFuncDecl := *m.Decl
|
||||
newFuncDecl.Recv = &newFieldList
|
||||
|
||||
// copy existing function documentation and set new declaration
|
||||
newM := *m
|
||||
newM.Decl = &newFuncDecl
|
||||
newM.Recv = typ
|
||||
|
||||
return &newM
|
||||
}
|
||||
|
||||
func makeBugDocs(list []*ast.CommentGroup) []string {
|
||||
d := make([]string, len(list))
|
||||
for i, g := range list {
|
||||
d[i] = CommentText(g)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// PackageDoc is the documentation for an entire package.
|
||||
//
|
||||
type PackageDoc struct {
|
||||
PackageName string
|
||||
ImportPath string
|
||||
Filenames []string
|
||||
Doc string
|
||||
Consts []*ValueDoc
|
||||
Types []*TypeDoc
|
||||
Vars []*ValueDoc
|
||||
Funcs []*FuncDoc
|
||||
Bugs []string
|
||||
}
|
||||
|
||||
// newDoc returns the accumulated documentation for the package.
|
||||
//
|
||||
func (doc *docReader) newDoc(importpath string, filenames []string) *PackageDoc {
|
||||
p := new(PackageDoc)
|
||||
p.PackageName = doc.pkgName
|
||||
p.ImportPath = importpath
|
||||
sort.Strings(filenames)
|
||||
p.Filenames = filenames
|
||||
p.Doc = CommentText(doc.doc)
|
||||
// makeTypeDocs may extend the list of doc.values and
|
||||
// doc.funcs and thus must be called before any other
|
||||
// function consuming those lists
|
||||
p.Types = doc.makeTypeDocs(doc.types)
|
||||
p.Consts = makeValueDocs(doc.values, token.CONST)
|
||||
p.Vars = makeValueDocs(doc.values, token.VAR)
|
||||
p.Funcs = makeFuncDocs(doc.funcs)
|
||||
p.Bugs = makeBugDocs(doc.bugs)
|
||||
return p
|
||||
|
||||
// process files in sorted order
|
||||
for _, filename := range filenames {
|
||||
f := pkg.Files[filename]
|
||||
if mode&AllDecls == 0 {
|
||||
r.fileExports(f)
|
||||
}
|
||||
r.addFile(f)
|
||||
}
|
||||
return r.newDoc(importpath, filenames)
|
||||
}
|
||||
|
|
137
libgo/go/go/doc/doc_test.go
Normal file
137
libgo/go/go/doc/doc_test.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
// 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 doc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"testing"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type sources map[string]string // filename -> file contents
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
importPath string
|
||||
mode Mode
|
||||
srcs sources
|
||||
doc string
|
||||
}
|
||||
|
||||
var tests = make(map[string]*testCase)
|
||||
|
||||
// To register a new test case, use the pattern:
|
||||
//
|
||||
// var _ = register(&testCase{ ... })
|
||||
//
|
||||
// (The result value of register is always 0 and only present to enable the pattern.)
|
||||
//
|
||||
func register(test *testCase) int {
|
||||
if _, found := tests[test.name]; found {
|
||||
panic(fmt.Sprintf("registration failed: test case %q already exists", test.name))
|
||||
}
|
||||
tests[test.name] = test
|
||||
return 0
|
||||
}
|
||||
|
||||
func runTest(t *testing.T, test *testCase) {
|
||||
// create AST
|
||||
fset := token.NewFileSet()
|
||||
var pkg ast.Package
|
||||
pkg.Files = make(map[string]*ast.File)
|
||||
for filename, src := range test.srcs {
|
||||
file, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Errorf("test %s: %v", test.name, err)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case pkg.Name == "":
|
||||
pkg.Name = file.Name.Name
|
||||
case pkg.Name != file.Name.Name:
|
||||
t.Errorf("test %s: different package names in test files", test.name)
|
||||
return
|
||||
}
|
||||
pkg.Files[filename] = file
|
||||
}
|
||||
|
||||
doc := New(&pkg, test.importPath, test.mode).String()
|
||||
if doc != test.doc {
|
||||
//TODO(gri) Enable this once the sorting issue of comments is fixed
|
||||
//t.Errorf("test %s\n\tgot : %s\n\twant: %s", test.name, doc, test.doc)
|
||||
}
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
runTest(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Printing support
|
||||
|
||||
func (pkg *Package) String() string {
|
||||
var buf bytes.Buffer
|
||||
docText.Execute(&buf, pkg) // ignore error - test will fail w/ incorrect output
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// TODO(gri) complete template
|
||||
var docText = template.Must(template.New("docText").Parse(
|
||||
`
|
||||
PACKAGE {{.Name}}
|
||||
DOC {{printf "%q" .Doc}}
|
||||
IMPORTPATH {{.ImportPath}}
|
||||
FILENAMES {{.Filenames}}
|
||||
`))
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Test cases
|
||||
|
||||
// Test that all package comments and bugs are collected,
|
||||
// and that the importPath is correctly set.
|
||||
//
|
||||
var _ = register(&testCase{
|
||||
name: "p",
|
||||
importPath: "p",
|
||||
srcs: sources{
|
||||
"p1.go": "// comment 1\npackage p\n//BUG(uid): bug1",
|
||||
"p0.go": "// comment 0\npackage p\n// BUG(uid): bug0",
|
||||
},
|
||||
doc: `
|
||||
PACKAGE p
|
||||
DOC "comment 0\n\ncomment 1\n"
|
||||
IMPORTPATH p
|
||||
FILENAMES [p0.go p1.go]
|
||||
`,
|
||||
})
|
||||
|
||||
// Test basic functionality.
|
||||
//
|
||||
var _ = register(&testCase{
|
||||
name: "p1",
|
||||
importPath: "p",
|
||||
srcs: sources{
|
||||
"p.go": `
|
||||
package p
|
||||
import "a"
|
||||
const pi = 3.14 // pi
|
||||
type T struct{} // T
|
||||
var V T // v
|
||||
func F(x int) int {} // F
|
||||
`,
|
||||
},
|
||||
doc: `
|
||||
PACKAGE p
|
||||
DOC ""
|
||||
IMPORTPATH p
|
||||
FILENAMES [p.go]
|
||||
`,
|
||||
})
|
|
@ -35,7 +35,7 @@ func Examples(pkg *ast.Package) []*Example {
|
|||
examples = append(examples, &Example{
|
||||
Name: name[len("Example"):],
|
||||
Body: &printer.CommentedNode{f.Body, src.Comments},
|
||||
Output: CommentText(f.Doc),
|
||||
Output: f.Doc.Text(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ func baseName(x ast.Expr) *ast.Ident {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (doc *docReader) filterFieldList(fields *ast.FieldList) (removedFields bool) {
|
||||
func (doc *docReader) filterFieldList(tinfo *typeInfo, fields *ast.FieldList) (removedFields bool) {
|
||||
if fields == nil {
|
||||
return false
|
||||
}
|
||||
|
@ -44,7 +44,18 @@ func (doc *docReader) filterFieldList(fields *ast.FieldList) (removedFields bool
|
|||
if len(f.Names) == 0 {
|
||||
// anonymous field
|
||||
name := baseName(f.Type)
|
||||
keepField = name != nil && name.IsExported()
|
||||
if name != nil && name.IsExported() {
|
||||
// we keep the field - in this case doc.addDecl
|
||||
// will take care of adding the embedded type
|
||||
keepField = true
|
||||
} else if tinfo != nil {
|
||||
// we don't keep the field - add it as an embedded
|
||||
// type so we won't loose its methods, if any
|
||||
if embedded := doc.lookupTypeInfo(name.Name); embedded != nil {
|
||||
_, ptr := f.Type.(*ast.StarExpr)
|
||||
tinfo.addEmbeddedType(embedded, ptr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
n := len(f.Names)
|
||||
f.Names = filterIdentList(f.Names)
|
||||
|
@ -54,7 +65,7 @@ func (doc *docReader) filterFieldList(fields *ast.FieldList) (removedFields bool
|
|||
keepField = len(f.Names) > 0
|
||||
}
|
||||
if keepField {
|
||||
doc.filterType(f.Type)
|
||||
doc.filterType(nil, f.Type)
|
||||
list[j] = f
|
||||
j++
|
||||
}
|
||||
|
@ -72,23 +83,23 @@ func (doc *docReader) filterParamList(fields *ast.FieldList) bool {
|
|||
}
|
||||
var b bool
|
||||
for _, f := range fields.List {
|
||||
if doc.filterType(f.Type) {
|
||||
if doc.filterType(nil, f.Type) {
|
||||
b = true
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (doc *docReader) filterType(typ ast.Expr) bool {
|
||||
func (doc *docReader) filterType(tinfo *typeInfo, typ ast.Expr) bool {
|
||||
switch t := typ.(type) {
|
||||
case *ast.Ident:
|
||||
return ast.IsExported(t.Name)
|
||||
case *ast.ParenExpr:
|
||||
return doc.filterType(t.X)
|
||||
return doc.filterType(nil, t.X)
|
||||
case *ast.ArrayType:
|
||||
return doc.filterType(t.Elt)
|
||||
return doc.filterType(nil, t.Elt)
|
||||
case *ast.StructType:
|
||||
if doc.filterFieldList(t.Fields) {
|
||||
if doc.filterFieldList(tinfo, t.Fields) {
|
||||
t.Incomplete = true
|
||||
}
|
||||
return len(t.Fields.List) > 0
|
||||
|
@ -97,16 +108,16 @@ func (doc *docReader) filterType(typ ast.Expr) bool {
|
|||
b2 := doc.filterParamList(t.Results)
|
||||
return b1 || b2
|
||||
case *ast.InterfaceType:
|
||||
if doc.filterFieldList(t.Methods) {
|
||||
if doc.filterFieldList(tinfo, t.Methods) {
|
||||
t.Incomplete = true
|
||||
}
|
||||
return len(t.Methods.List) > 0
|
||||
case *ast.MapType:
|
||||
b1 := doc.filterType(t.Key)
|
||||
b2 := doc.filterType(t.Value)
|
||||
b1 := doc.filterType(nil, t.Key)
|
||||
b2 := doc.filterType(nil, t.Value)
|
||||
return b1 || b2
|
||||
case *ast.ChanType:
|
||||
return doc.filterType(t.Value)
|
||||
return doc.filterType(nil, t.Value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -116,12 +127,12 @@ func (doc *docReader) filterSpec(spec ast.Spec) bool {
|
|||
case *ast.ValueSpec:
|
||||
s.Names = filterIdentList(s.Names)
|
||||
if len(s.Names) > 0 {
|
||||
doc.filterType(s.Type)
|
||||
doc.filterType(nil, s.Type)
|
||||
return true
|
||||
}
|
||||
case *ast.TypeSpec:
|
||||
if ast.IsExported(s.Name.Name) {
|
||||
doc.filterType(s.Type)
|
||||
doc.filterType(doc.lookupTypeInfo(s.Name.Name), s.Type)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ func matchDecl(d *ast.GenDecl, f Filter) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func filterValueDocs(a []*ValueDoc, f Filter) []*ValueDoc {
|
||||
func filterValues(a []*Value, f Filter) []*Value {
|
||||
w := 0
|
||||
for _, vd := range a {
|
||||
if matchDecl(vd.Decl, f) {
|
||||
|
@ -60,7 +60,7 @@ func filterValueDocs(a []*ValueDoc, f Filter) []*ValueDoc {
|
|||
return a[0:w]
|
||||
}
|
||||
|
||||
func filterFuncDocs(a []*FuncDoc, f Filter) []*FuncDoc {
|
||||
func filterFuncs(a []*Func, f Filter) []*Func {
|
||||
w := 0
|
||||
for _, fd := range a {
|
||||
if f(fd.Name) {
|
||||
|
@ -71,7 +71,18 @@ func filterFuncDocs(a []*FuncDoc, f Filter) []*FuncDoc {
|
|||
return a[0:w]
|
||||
}
|
||||
|
||||
func filterTypeDocs(a []*TypeDoc, f Filter) []*TypeDoc {
|
||||
func filterMethods(a []*Method, f Filter) []*Method {
|
||||
w := 0
|
||||
for _, md := range a {
|
||||
if f(md.Name) {
|
||||
a[w] = md
|
||||
w++
|
||||
}
|
||||
}
|
||||
return a[0:w]
|
||||
}
|
||||
|
||||
func filterTypes(a []*Type, f Filter) []*Type {
|
||||
w := 0
|
||||
for _, td := range a {
|
||||
n := 0 // number of matches
|
||||
|
@ -79,11 +90,11 @@ func filterTypeDocs(a []*TypeDoc, f Filter) []*TypeDoc {
|
|||
n = 1
|
||||
} else {
|
||||
// type name doesn't match, but we may have matching consts, vars, factories or methods
|
||||
td.Consts = filterValueDocs(td.Consts, f)
|
||||
td.Vars = filterValueDocs(td.Vars, f)
|
||||
td.Factories = filterFuncDocs(td.Factories, f)
|
||||
td.Methods = filterFuncDocs(td.Methods, f)
|
||||
n += len(td.Consts) + len(td.Vars) + len(td.Factories) + len(td.Methods)
|
||||
td.Consts = filterValues(td.Consts, f)
|
||||
td.Vars = filterValues(td.Vars, f)
|
||||
td.Funcs = filterFuncs(td.Funcs, f)
|
||||
td.Methods = filterMethods(td.Methods, f)
|
||||
n += len(td.Consts) + len(td.Vars) + len(td.Funcs) + len(td.Methods)
|
||||
}
|
||||
if n > 0 {
|
||||
a[w] = td
|
||||
|
@ -96,10 +107,10 @@ func filterTypeDocs(a []*TypeDoc, f Filter) []*TypeDoc {
|
|||
// Filter eliminates documentation for names that don't pass through the filter f.
|
||||
// TODO: Recognize "Type.Method" as a name.
|
||||
//
|
||||
func (p *PackageDoc) Filter(f Filter) {
|
||||
p.Consts = filterValueDocs(p.Consts, f)
|
||||
p.Vars = filterValueDocs(p.Vars, f)
|
||||
p.Types = filterTypeDocs(p.Types, f)
|
||||
p.Funcs = filterFuncDocs(p.Funcs, f)
|
||||
func (p *Package) Filter(f Filter) {
|
||||
p.Consts = filterValues(p.Consts, f)
|
||||
p.Vars = filterValues(p.Vars, f)
|
||||
p.Types = filterTypes(p.Types, f)
|
||||
p.Funcs = filterFuncs(p.Funcs, f)
|
||||
p.Doc = "" // don't show top-level package doc
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue