Compare commits

...

86 Commits

Author SHA1 Message Date
mappu c1c94112c0 doc/README: add links to archived releases 2020-05-06 18:44:09 +12:00
mappu 3d9fdc8669 doc: update README 2018-12-31 18:50:23 +13:00
mappu 0556358529 doc: top-level README.md 2018-12-31 18:33:13 +13:00
mappu ea3bb84b7b build: remove Dep references in Makefile 2018-12-31 18:30:05 +13:00
mappu 1c6e3b1c9d build: remove dep meta files, readd vendor directory 2018-12-31 18:22:33 +13:00
mappu a74d57e7fc build: convert to Go Modules 2018-12-31 18:22:13 +13:00
mappu 8f443e1cfb vendor: commit existing golang vendor directory 2018-12-31 18:21:15 +13:00
mappu aa7557ed47 hg2git: convert ignores/tags files 2018-12-31 18:20:53 +13:00
mappu 7936330903 makefile: bump version to 1.3.1 2017-11-26 19:57:40 +13:00
mappu 1e130ee932 Moved tag v1.3.0 to changeset 9547a8a3a8ea (from changeset 4dc4a77c8db3) 2017-11-26 19:56:53 +13:00
mappu f0e64cbc34 modernise the sample configuration file 2017-11-26 19:56:43 +13:00
mappu e7587dae2d Added tag v1.3.0 for changeset 4dc4a77c8db3 2017-11-26 19:55:50 +13:00
mappu 2e264ecaf1 doc: readme 2017-11-26 19:55:45 +13:00
mappu 046c6627ec makefile: bump version to 1.3.0 2017-11-26 19:55:39 +13:00
mappu 36f49749ad config: hub.port is optional, allow putting full URL in hub.address field 2017-11-26 19:54:03 +13:00
mappu aacebe5c5f compatibility updates for libnmdc 2017-11-26 19:51:10 +13:00
mappu 80d230be98 vendor: bump libnmdc 0.15.0 -> 0.16.0 2017-11-26 19:48:53 +13:00
mappu 27c9621833 makefile: bump version to 1.2.4 2017-11-14 19:58:00 +13:00
mappu 082502c7e6 Added tag v1.2.3 for changeset e7de5d5504b9 2017-11-14 19:57:50 +13:00
mappu f4684673e7 doc: readme 2017-11-14 19:57:44 +13:00
mappu 0dc5c2878f hgignore 2017-11-14 19:47:21 +13:00
mappu efdb188aba vendor: bump libnmdc 0.14.0 -> 0.15.0 2017-11-14 19:45:08 +13:00
mappu 54e21f2b2d clear owner/group from linux64 release tarball 2017-10-28 14:15:23 +13:00
mappu dd166cf0e9 doc: mention supercedes 2017-10-28 14:00:48 +13:00
mappu 92b1c4daa5 doc: fix cosmetic issue with readme generation on code.ivysaur.me 2017-10-28 13:43:02 +13:00
mappu ea4135888d bump all versions to 1.2.3 2017-10-28 13:32:29 +13:00
mappu 8e999f5ddb Added tag v1.2.2 for changeset 0c6b957de432 2017-10-28 13:32:16 +13:00
mappu 62c03cf9f1 doc: mention download options 2017-10-28 13:30:51 +13:00
mappu 6abdc8d2a9 server: use real content length, use system MIME info instead of handrolling it 2017-10-28 13:27:44 +13:00
mappu 1a281095f0 regenerate bindata.go 2017-10-28 13:27:19 +13:00
mappu 69613d07a5 build: remove timestamps from generated bindata.go file 2017-10-28 13:27:07 +13:00
mappu 19b2221ea1 doc: mention unminified development instructions 2017-10-28 13:24:18 +13:00
mappu 9840c1024e doc: preliminary changelog update 2017-10-28 13:17:50 +13:00
mappu 00cdce5f5a doc: add instructions on setting up a development environment 2017-10-28 13:17:13 +13:00
mappu 312cbb7bc1 commit a copy of bindata.go, to allow installation via go get 2017-10-28 13:11:05 +13:00
mappu bea598f7a3 retag in vX.Y.Z format 2017-10-28 12:53:54 +13:00
mappu ef9a61e9cf hgignore vendor directory 2017-10-28 12:49:10 +13:00
mappu 0ec079cfd4 use 'dep' for dependency management 2017-10-28 12:48:55 +13:00
mappu 54ecffd0d4 build: remove dead code 2017-10-28 11:56:11 +13:00
mappu 41e244c255 build: replace minipack shell script with sed commands 2017-10-28 11:51:23 +13:00
mappu a109e2dc29 build: replace IIFEMODE/sed with cat in makefile 2017-10-28 11:40:09 +13:00
mappu 0bafa59358 build: remove php, replace with bash script 2017-10-28 11:32:51 +13:00
mappu 78ced31e9d further conversion to makefile build 2017-10-28 00:21:20 +13:00
mappu 16a4a2556b partial conversion to makefile build 2017-10-28 00:06:45 +13:00
mappu fafca96711 fix not being able to close PM tabs 2017-10-27 23:44:18 +13:00
mappu 2eacb06c9e Added tag release-1.2.1 for changeset c8cd84947e45 2017-10-16 18:48:21 +13:00
mappu 1f2871b917 doc: preliminary changelog update 2017-10-16 18:47:32 +13:00
mappu 855206feed hide (0) from page title 2017-10-16 18:33:43 +13:00
mappu efa195df5b bump scrollback limit from 50->200 2017-10-16 18:30:56 +13:00
mappu 89c8cb0dc8 contented: fix upload item disappearing once logged in 2017-10-15 22:55:00 +13:00
mappu c2de4c8d45 Added tag release-1.2.0 for changeset 0eeab5594ba4 2017-10-15 22:54:41 +13:00
mappu 738e9aed41 doc: preliminary changelog update 2017-10-15 22:06:35 +13:00
mappu bdb7b4835d fixes for previous 2017-10-15 22:03:27 +13:00
mappu 447e8c2591 js: initial contented implementation 2017-10-15 20:56:47 +13:00
mappu 37014f7f52 server: send contented_server option down websocket to client 2017-10-15 20:56:38 +13:00
mappu b8791f6aa2 js: rename $ -> el for jQuery compat 2017-10-15 20:56:23 +13:00
mappu 377131e43d replace menu icon from astral unicode to mini svg 2017-10-15 20:30:06 +13:00
mappu 20fa044906 whitespace 2017-10-15 20:24:56 +13:00
mappu 0cb14fb964 build: remove win64, linux32 targets 2017-10-15 20:22:15 +13:00
mappu a60d821e39 build: expand clientpack cleanup commands 2017-10-15 20:20:52 +13:00
mappu 15d9ee0023 update uglifyjs dependency install script 2017-10-15 20:13:23 +13:00
mappu ed0195c4b8 chmod +x 2017-10-15 20:07:26 +13:00
mappu a71eb45dd5 Added tag release-1.1.4 for changeset 76c178b8f27e 2017-02-11 14:18:13 +13:00
mappu 086ecfb0a9 readme 2017-02-11 14:14:19 +13:00
mappu dff36f6bff update godist 2017-02-11 14:13:58 +13:00
mappu 7a9b271b90 Added tag release-1.1.3 for changeset 6cbd9d596303 2017-02-11 13:13:27 +13:00
mappu 1c7842c182 readme 2017-02-11 13:13:09 +13:00
mappu 9e33e50986 display ip addresses 2017-02-09 19:28:43 +13:00
mappu 7f618db70a fix not displaying nonzero share sizes 2017-02-08 19:02:20 +13:00
mappu 7894355647 popups: only show when manually turning on the feature, not when it's auto-enabled on load 2017-02-08 18:34:12 +13:00
mappu af324441b7 client: clicking the popup refocus browser window, switch to PM tab as necessary 2017-02-08 18:33:17 +13:00
mappu d862a3f703 more consistent comment style for URL references 2017-02-08 18:20:35 +13:00
mappu 59c118dc34 remove queryselectorall, always use the fallback implementation 2017-02-08 18:19:57 +13:00
mappu 42a8298362 standardise event cancellation 2017-02-08 18:19:33 +13:00
mappu c4e37bf47d explicit falsey checks for some persistent vars (no practical difference) 2017-02-06 16:49:37 +13:00
mappu 95d56dbca2 Added tag release-1.1.2 for changeset 7278eb0d067d 2017-02-06 16:44:27 +13:00
mappu 761d0bfad5 build: set build version in binary at build time 2017-02-06 16:43:17 +13:00
mappu 1d3f16e6c6 change default hub name in sample file 2017-02-06 16:43:07 +13:00
mappu e5ceadb03a doc: changelog 2017-02-06 16:39:27 +13:00
mappu 6999069fd7 server: set SERVER header on all requests (even SIO ones), display version on startup 2017-02-06 16:35:25 +13:00
mappu 5da61d5922 whitespace in sample config file 2017-02-06 16:27:33 +13:00
mappu d33ba5c085 remove 'extern' from config files 2017-02-06 16:27:14 +13:00
mappu a28e5ce9b0 remove extra /conf/ request 2017-02-06 16:25:49 +13:00
mappu 3448cb7eeb don't require extern 2017-02-06 16:20:31 +13:00
mappu ba941adfdd client: fix padding around user count in title 2017-02-06 13:54:24 +13:00
mappu 48d96f9efe Added tag release-1.1.1 for changeset d14041daa7bb 2017-02-06 13:46:20 +13:00
100 changed files with 9440 additions and 500 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
nmdc-webfrontend
nmdc-webfrontend.exe
nmdc-webfrontend.conf
clientpack/
_dist/
node_modules/

View File

@ -1,8 +0,0 @@
mode:regex
\.exe$
^nmdc-webfrontend\.conf$
^clientpack/
^_dist/
^bindata\.go$

View File

@ -1,4 +0,0 @@
769fad81e3f8db8f7e5f5c164656a382a169d735 release-1.0.0
9ed95938d809a8226aca529e34b655e6d8c8c379 release-1.0.1
46fe533682419c8a519836ac95b5575053aa0fa8 release-1.0.2
a2c92b262f339f82eb01c8d92dda252a27432255 release-1.1.0

View File

@ -2,13 +2,13 @@ package main
type Config struct {
App struct {
MotdHTML string `json:"motd"`
MotdHTML string `json:"motd"`
ContentedServer string `json:"contented_server"`
}
Web struct {
Port int `json:"port"`
BindTo string `json:"bind_to"`
Extern string `json:"extern"`
Title string `json:"title"`
CustomFavicon bool `json:"custom_favicon"`
@ -17,7 +17,7 @@ type Config struct {
Hub struct {
Address string `json:"address"`
Port int `json:"port"`
Port int `json:"port,omitempty"`
Tag string `json:"tag"`
}
}

58
Makefile Normal file
View File

@ -0,0 +1,58 @@
# Makefile for nmdc-webfrontend
BINNAME=nmdc-webfrontend
VERSION=1.3.1
GOFLAGS=-a \
-ldflags "-s -w -X main.VERSION=$(BINNAME)/$(VERSION)" \
-gcflags "-trimpath ${GOPATH}" \
-asmflags "-trimpath ${GOPATH}"
SOURCES=client/ go.mod go.sum Makefile Config.go main.go nmdc-webfrontend.conf.SAMPLE
.PHONY: all deps clean
all: $(BINNAME)-$(VERSION)-win32.7z $(BINNAME)-$(VERSION)-linux64.tar.xz $(BINNAME)-$(VERSION)-src.tar.xz
deps:
npm install -g less uglify-js less-plugin-clean-css html-minifier
go get -u github.com/jteeuwen/go-bindata/...
clean:
rm -f ./$(BINNAME)
rm -f ./$(BINNAME).exe
rm -fr ./clientpack
rm -f ./bindata.go
bindata.go: client client/*
rm -fr ./clientpack
cp -r ./client ./clientpack
( echo ';(function() {' ; cat clientpack/dcwebui.js ; echo '})();' ) | uglifyjs -o clientpack/dcwebui.min.js -c -m --ie8
lessc --clean-css clientpack/dcwebui.css clientpack/dcwebui.min.css
cat clientpack/index.htm \
| sed -e '/dcwebui.css/{i <style>' -e 'r clientpack/dcwebui.min.css' -e 'a </style>' -e 'd}' \
| sed -e '/dcwebui.js/{i <script>' -e 'r clientpack/dcwebui.min.js' -e 'a </script>' -e 'd}' \
| sed -e '/socket.io-1.7.2.js/{i <script>' -e 'r clientpack/socket.io-1.7.2.js' -e 'a </script>' -e 'd}' \
> clientpack/index.packed.htm
mv clientpack/index.packed.htm clientpack/index.htm
html-minifier --collapse-whitespace -o clientpack/index.min.htm clientpack/index.htm
mv clientpack/index.min.htm clientpack/index.htm
rm ./clientpack/*.js
rm ./clientpack/*.css
go-bindata -nomemcopy -nometadata -prefix clientpack clientpack
$(BINNAME).exe: bindata.go *.go
GOARCH=386 GOOS=windows go build $(GOFLAGS) -o $(BINNAME).exe
$(BINNAME): bindata.go *.go
GOARCH=amd64 GOOS=linux go build $(GOFLAGS) -o $(BINNAME)
$(BINNAME)-$(VERSION)-win32.7z: $(BINNAME).exe nmdc-webfrontend.conf.SAMPLE
7z a -mx9 "$(BINNAME)-$(VERSION)-win32.7z" $(BINNAME).exe nmdc-webfrontend.conf.SAMPLE
$(BINNAME)-$(VERSION)-linux64.tar.xz: $(BINNAME) nmdc-webfrontend.conf.SAMPLE
XZ_OPT='-9' tar caf "$(BINNAME)-$(VERSION)-linux64.tar.xz" $(BINNAME) nmdc-webfrontend.conf.SAMPLE --owner=0 --group=0
$(BINNAME)-$(VERSION)-src.tar.xz: $(SOURCES)
XZ_OPT='-9' tar caf "$(BINNAME)-$(VERSION)-src.tar.xz" $(SOURCES) --owner=0 --group=0

165
README.md Normal file
View File

@ -0,0 +1,165 @@
# nmdc-webfrontend
A web interface to an NMDC/ADC hub.
Chat in real-time on your NMDC/ADC hub using a web browser.
## Relationship to other projects
This project forks and deprecates my earlier `dcwebui2` project since Go seems to use less memory than node.
This project supercedes [dcwebui](https://code.ivysaur.me/dcwebui/), [flexdc](https://code.ivysaur.me/flexdc/), and [dcwebui2](https://code.ivysaur.me/dcwebui2/).
### Upgrading from `dcwebui2`
- The configuration file content is identical between `nmdc-webfrontend` 1.0.0 and `dcwebui2` 1.3.0, but please now ensure it's valid JSON instead of arbitrary javascript. This means no assignment, use double-quoted strings, and no comments.
- Future changes to the configuration file since `nmdc-webfrontend` 1.0.0 are backward compatible (see the changelog for more details).
## Developing
1. Install Go (>= 1.11), Node.js, NPM, and 7-Zip (`p7zip-full` on Debian)
2. Download the source code
- `git clone https://git.ivysaur.me/code.ivysaur.me/nmdc-webfrontend.git `; or
- `go get code.ivysaur.me/nmdc-webfrontend` ; or
- download and extract a source archive
3. Install dependencies: `sudo make deps`
4. Build: `make`
5. Optional: Set `web.external_webroot: true` in the config file for unminified development
## Changelog
2017-11-26 1.3.0
- Update libnmdc to 0.16 (adds ADC hub support)
- Configuration: The `hub.port` property is now optional. You can specify a full URI in the `hub.address` property instead.
- [⬇️ nmdc-webfrontend-1.3.0-win32.7z](https://git.ivysaur.me/attachments/76e6b425-d34d-4451-815b-98a6e0685a19) *(1.44 MiB)*
- [⬇️ nmdc-webfrontend-1.3.0-src.tar.xz](https://git.ivysaur.me/attachments/a362dd86-2999-4753-adac-830f1a1a64ac) *(64.81 KiB)*
- [⬇️ nmdc-webfrontend-1.3.0-linux64.tar.xz](https://git.ivysaur.me/attachments/877fc7c9-0df8-4514-9147-7f102f221d49) *(1.69 MiB)*
2017-11-14 1.2.3
- Update libnmdc to 0.15
- [⬇️ nmdc-webfrontend-1.2.3-win32.7z](https://git.ivysaur.me/attachments/02c33e38-89ef-4cbd-b8a2-80cbd6077520) *(1.41 MiB)*
- [⬇️ nmdc-webfrontend-1.2.3-src.tar.xz](https://git.ivysaur.me/attachments/e3e7599d-4805-4e39-a45e-d7f68cc32f09) *(64.72 KiB)*
- [⬇️ nmdc-webfrontend-1.2.3-linux64.tar.xz](https://git.ivysaur.me/attachments/c5a06c20-e14a-4cfd-b830-7b41a52074c1) *(1.66 MiB)*
2017-10-28 1.2.2
- Enhancement: Simplify build process
- Fix an issue with closing PM tabs
- [⬇️ nmdc-webfrontend-1.2.2-win32.7z](https://git.ivysaur.me/attachments/37f3a7f4-637b-44d7-9575-5a721a504ce5) *(1.41 MiB)*
- [⬇️ nmdc-webfrontend-1.2.2-src.tar.xz](https://git.ivysaur.me/attachments/265ed79c-901f-43e7-9746-99b45a28311b) *(64.72 KiB)*
- [⬇️ nmdc-webfrontend-1.2.2-linux64.tar.xz](https://git.ivysaur.me/attachments/74ebeaaa-a2ef-4b9c-948f-8c11522d5ab0) *(1.66 MiB)*
2017-10-16 1.2.1
- Enhancement: Increase scrollback buffer size
- Fix an issue with missing `contented` upload link once logged in
- Fix a cosmetic issue with `(0)` appearing in page title
- [⬇️ nmdc-webfrontend-1.2.1-win32.7z](https://git.ivysaur.me/attachments/a99d5d16-968f-4b02-954a-e22314ae9d64) *(1.39 MiB)*
- [⬇️ nmdc-webfrontend-1.2.1-src.tar.xz](https://git.ivysaur.me/attachments/e9c3dd9f-c0b1-4913-8315-3a39c809155c) *(65.74 KiB)*
- [⬇️ nmdc-webfrontend-1.2.1-linux64.tar.xz](https://git.ivysaur.me/attachments/bf3dc821-182b-4911-a46f-72efbc4310f8) *(1.64 MiB)*
2017-10-15 1.2.0
- Feature: Add `contented` integration (set `app.contented_server` in config file)
- Fix a cosmetic issue with the menu icon on devices without unicode font coverage
- [⬇️ nmdc-webfrontend-1.2.0-win32.7z](https://git.ivysaur.me/attachments/48c194e1-6463-4449-a8d4-e2b1cef2a522) *(1.39 MiB)*
- [⬇️ nmdc-webfrontend-1.2.0-src.tar.xz](https://git.ivysaur.me/attachments/ceba8731-b0e6-483e-8282-360a0a584304) *(65.69 KiB)*
- [⬇️ nmdc-webfrontend-1.2.0-linux64.tar.xz](https://git.ivysaur.me/attachments/6082cb13-0e62-4fa8-95c6-1b7d7f7a3272) *(1.64 MiB)*
2017-02-11 1.1.4
- Update libnmdc to 0.14
- [⬇️ nmdc-webfrontend-1.1.4-win64.7z](https://git.ivysaur.me/attachments/b104b02c-9469-4b8d-be9f-3fbafecb4edc) *(1.36 MiB)*
- [⬇️ nmdc-webfrontend-1.1.4-win32.7z](https://git.ivysaur.me/attachments/dd0a9bab-6da6-45d4-a48f-51c9621e9c55) *(1.25 MiB)*
- [⬇️ nmdc-webfrontend-1.1.4-src.tar.xz](https://git.ivysaur.me/attachments/cc0be981-bad7-401e-aace-ac6d8d9a56dd) *(65.53 KiB)*
- [⬇️ nmdc-webfrontend-1.1.4-linux64.tar.xz](https://git.ivysaur.me/attachments/7cd26e52-9ea7-4dcc-91b3-273bc0a3eda4) *(1.50 MiB)*
- [⬇️ nmdc-webfrontend-1.1.4-linux32.tar.xz](https://git.ivysaur.me/attachments/15895b23-a582-4d59-91da-36f6e6145dfe) *(1.41 MiB)*
2017-02-11 1.1.3
- Feature: Display user IP address on hover, if available
- Enhancement: Allow clicking on popup notifications
- Enhancement: Only show 'popups enabled' notification when enabling for the first time, not persistent page load
- Update libnmdc to 0.13
- Fix a cosmetic issue with not displaying non-zero user share sizes
- [⬇️ nmdc-webfrontend-1.1.3-win64.7z](https://git.ivysaur.me/attachments/d7b551fb-0dd4-4c38-bbe2-513c67311752) *(1.36 MiB)*
- [⬇️ nmdc-webfrontend-1.1.3-win32.7z](https://git.ivysaur.me/attachments/33b90056-bff3-488c-bf01-bcaf7f114176) *(1.25 MiB)*
- [⬇️ nmdc-webfrontend-1.1.3-src.tar.xz](https://git.ivysaur.me/attachments/0390f266-3663-48b5-9216-4a3e41623017) *(65.50 KiB)*
- [⬇️ nmdc-webfrontend-1.1.3-linux64.tar.xz](https://git.ivysaur.me/attachments/8ab5ac7c-e8d2-47c0-886a-90bfaf011efd) *(1.50 MiB)*
- [⬇️ nmdc-webfrontend-1.1.3-linux32.tar.xz](https://git.ivysaur.me/attachments/229ef410-19a1-4fa8-8625-473bf486b40f) *(1.41 MiB)*
2017-02-06 1.1.2
- Autodetect 'extern', no need to include it in config files
- Enhancement: Remove redundant request, for a faster page load
- Display server version number in log file and in response headers
- Fix a cosmetic issue with spacing around user count in page title
- [⬇️ nmdc-webfrontend-1.1.2-win64.7z](https://git.ivysaur.me/attachments/96c0305e-dc3e-45c6-9567-5eaa461551ec) *(1.36 MiB)*
- [⬇️ nmdc-webfrontend-1.1.2-win32.7z](https://git.ivysaur.me/attachments/27095754-735c-4e24-8cff-60bdbbe60b34) *(1.25 MiB)*
- [⬇️ nmdc-webfrontend-1.1.2-src.tar.xz](https://git.ivysaur.me/attachments/5b9d177e-f79d-4afd-a97c-6d563ba0e7af) *(65.42 KiB)*
- [⬇️ nmdc-webfrontend-1.1.2-linux64.tar.xz](https://git.ivysaur.me/attachments/a2325dc5-992e-4d45-a622-c516e41a3187) *(1.50 MiB)*
- [⬇️ nmdc-webfrontend-1.1.2-linux32.tar.xz](https://git.ivysaur.me/attachments/e2dee266-1b9d-4837-9536-98b1922eefa0) *(1.41 MiB)*
2017-02-06 1.1.1
- Fix an issue with malformed content in minified build
- [⬇️ nmdc-webfrontend-1.1.1-win64.7z](https://git.ivysaur.me/attachments/412f04e2-08f5-4a12-b933-43ea00164fb2) *(1.36 MiB)*
- [⬇️ nmdc-webfrontend-1.1.1-win32.7z](https://git.ivysaur.me/attachments/52753dca-3dd6-4668-a785-2d5807d27e63) *(1.25 MiB)*
- [⬇️ nmdc-webfrontend-1.1.1-src.tar.xz](https://git.ivysaur.me/attachments/e9beee17-8368-4d21-b33f-9e3fa2e11c74) *(65.39 KiB)*
- [⬇️ nmdc-webfrontend-1.1.1-linux64.tar.xz](https://git.ivysaur.me/attachments/e93a09fc-7d0c-4b2a-a706-dddd51d9426d) *(1.50 MiB)*
- [⬇️ nmdc-webfrontend-1.1.1-linux32.tar.xz](https://git.ivysaur.me/attachments/bb9daa45-1bdb-492d-9d55-5fc39a046433) *(1.41 MiB)*
2017-02-06 1.1.0
- Feature: Remember last username/password for login; remember last "show joins/parts" status
- Feature: Display user details on hover (description, email, client tag, share size)
- Feature: Optional desktop notifications for background PMs (not possible in incognito)
- Feature: Automatically reconnect with the same username/password if connection was lost
- Feature: Re-enter last message (Ctrl+Up, Ctrl+Down)
- Feature: Set custom date/time format (Minutes, Seconds, Full), remembered for next session
- Feature: Clickable magnet links
- Feature: Display unread main-chat message count in the page title if the window is inactive
- Feature: Add warning message when closing tab while still connected (optional preference, disabled by default, will be remembered).
- Feature: Admin option to load a custom favicon (set `web.custom_favicon=true` and place a `favicon.ico` in the current directory)
- Feature: Admin option to use external web resources (set `web.external_webroot=true` and use the /client/ directory)
- Enhancement: Higher resolution favicon
- Enhancement: Display operators in green in the user list
- Enhancement: Enable spellcheck for text input once logged in
- Enhancement: Prevent sending referrer to remote URLs
- Enhancement: Display joins/parts and connection/disconnection messages in PM tabs
- Enhancement: Support Shift+Tab to autocomplete backward
- Enhancement: Support unread status for the main tab
- Enhancement: Improve page load time via minification
- Remove unused options from the config file
- Update socket.io to 1.7.2
- Update libnmdc to 0.12
- Add margin between bottom of the text area and the text input box
- Fix a cosmetic issue with collapsing consecutive spaces in posted messages
- Fix a cosmetic issue with text size adjustment on mobile devices
- Fix a cosmetic issue with clearing the screen on reconnection
- Fix a cosmetic issue with not clearing the userlist on certain types of network error
- Fix a cosmetic issue with closed PM tabs reappearing in some cases
- Fix a cosmetic issue with marking all PM tabs as read when switching to a single one
- [⬇️ nmdc-webfrontend-1.1.0-win64.7z](https://git.ivysaur.me/attachments/a88be3f1-a20f-4ae8-8723-fb538e321a73) *(1.36 MiB)*
- [⬇️ nmdc-webfrontend-1.1.0-win32.7z](https://git.ivysaur.me/attachments/09326166-ef9e-4901-91f9-ad3b0fff156f) *(1.25 MiB)*
- [⬇️ nmdc-webfrontend-1.1.0-src.tar.xz](https://git.ivysaur.me/attachments/6d83cf30-3436-4c5d-a6e4-715703af7ce7) *(65.39 KiB)*
- [⬇️ nmdc-webfrontend-1.1.0-linux64.tar.xz](https://git.ivysaur.me/attachments/a9baf26c-1756-49d9-a8aa-720f01d72194) *(1.50 MiB)*
- [⬇️ nmdc-webfrontend-1.1.0-linux32.tar.xz](https://git.ivysaur.me/attachments/00e5a0a2-4b3f-4276-aefe-56046707a14b) *(1.41 MiB)*
2016-11-29 1.0.2
- Rebuild with libnmdc 0.11
- Fix an issue with not setting a version in the client tag
- [⬇️ nmdc-webfrontend-1.0.2-win64.7z](https://git.ivysaur.me/attachments/6b84047d-9a4c-45fe-985b-5321f629f806) *(1.34 MiB)*
- [⬇️ nmdc-webfrontend-1.0.2-win32.7z](https://git.ivysaur.me/attachments/1505b8fa-83e1-469d-8c77-acee3711e26f) *(1.23 MiB)*
- [⬇️ nmdc-webfrontend-1.0.2-src.tar.xz](https://git.ivysaur.me/attachments/faff2112-d191-4ee8-a572-553bc4a25be8) *(54.28 KiB)*
- [⬇️ nmdc-webfrontend-1.0.2-linux64.tar.xz](https://git.ivysaur.me/attachments/c319c140-8bed-4225-a953-e99940ba9732) *(1.48 MiB)*
- [⬇️ nmdc-webfrontend-1.0.2-linux32.tar.xz](https://git.ivysaur.me/attachments/a55fc471-5d80-49ad-8670-67cfb8c6175c) *(1.39 MiB)*
2016-10-08 1.0.1
- Fix an issue with backward compatibility with `dcwebui2` configuration file format
- [⬇️ nmdc-webfrontend-1.0.1-win64.7z](https://git.ivysaur.me/attachments/d5ed658d-1ef3-4bd8-b2de-3d9415322f57) *(1.35 MiB)*
- [⬇️ nmdc-webfrontend-1.0.1-win32.7z](https://git.ivysaur.me/attachments/546bd5f9-3742-498f-a1cb-85690c84ddc2) *(1.23 MiB)*
- [⬇️ nmdc-webfrontend-1.0.1-src.tar.xz](https://git.ivysaur.me/attachments/5ec729d7-d914-4b7e-ade3-21988c203652) *(54.25 KiB)*
- [⬇️ nmdc-webfrontend-1.0.1-linux64.tar.xz](https://git.ivysaur.me/attachments/d7bf1c9c-ab2c-4c61-b7d8-7b6233ae059f) *(1.48 MiB)*
- [⬇️ nmdc-webfrontend-1.0.1-linux32.tar.xz](https://git.ivysaur.me/attachments/d5265319-81e5-4f3d-95f7-a6e46c607490) *(1.39 MiB)*
2016-10-08 1.0.0
- Port `dcwebui2` from Node.js (Javascript) to Go
- Fix a cosmetic issue with not clearing userlist on disconnection
- [⬇️ nmdc-webfrontend-1.0.0-win64.7z](https://git.ivysaur.me/attachments/3b7386c8-1445-4590-9779-af9870ea542a) *(1.34 MiB)*
- [⬇️ nmdc-webfrontend-1.0.0-win32.7z](https://git.ivysaur.me/attachments/9ad9bd21-4152-462c-9ddc-b584917058e5) *(1.23 MiB)*
- [⬇️ nmdc-webfrontend-1.0.0-src.tar.xz](https://git.ivysaur.me/attachments/d2745543-671c-42b5-b334-2dba1ee8726c) *(54.23 KiB)*
- [⬇️ nmdc-webfrontend-1.0.0-linux64.tar.xz](https://git.ivysaur.me/attachments/6102edf8-471b-4793-8877-c747f5ac5680) *(1.48 MiB)*
- [⬇️ nmdc-webfrontend-1.0.0-linux32.tar.xz](https://git.ivysaur.me/attachments/5a4bcb39-41e4-4db9-80ff-c0ea04912b06) *(1.39 MiB)*

View File

@ -1,58 +0,0 @@
A web interface to an NMDC hub.
Chat in real-time on your NMDC hub using a web browser. This project forks and deprecates my earlier `dcwebui2` project since Go seems to use less memory than node.
Written in Golang
Tags: nmdc
=UPGRADING FROM DCWEBUI2=
- The configuration file format is identical, but please now ensure it's valid json instead of just a .js file. This means no assignment, use double-quoted strings, and no comments.
=CHANGELOG=
2017-02-06 1.1.1
- Fix an issue with malformed content in minified build
2017-02-06 1.1.0
- Feature: Remember last username/password for login; remember last "show joins/parts" status
- Feature: Display user details on hover (description, email, client tag, share size)
- Feature: Optional desktop notifications for background PMs (not possible in incognito)
- Feature: Automatically reconnect with the same username/password if connection was lost
- Feature: Re-enter last message (Ctrl+Up, Ctrl+Down)
- Feature: Set custom date/time format (Minutes, Seconds, Full), remembered for next session
- Feature: Clickable magnet links
- Feature: Display unread main-chat message count in the page title if the window is inactive
- Feature: Add warning message when closing tab while still connected (optional preference, disabled by default, will be remembered).
- Feature: Admin option to load a custom favicon (set `web.custom_favicon=true` and place a `favicon.ico` in the current directory)
- Feature: Admin option to use external web resources (set `web.external_webroot=true` and use the /client/ directory)
- Enhancement: Higher resolution favicon
- Enhancement: Display operators in green in the user list
- Enhancement: Enable spellcheck for text input once logged in
- Enhancement: Prevent sending referrer to remote URLs
- Enhancement: Display joins/parts and connection/disconnection messages in PM tabs
- Enhancement: Support Shift+Tab to autocomplete backward
- Enhancement: Support unread status for the main tab
- Enhancement: Improve page load time via minification
- Remove unused options from the config file
- Update socket.io to 1.7.2
- Update libnmdc to 0.12
- Add margin between bottom of the text area and the text input box
- Fix a cosmetic issue with collapsing consecutive spaces in posted messages
- Fix a cosmetic issue with text size adjustment on mobile devices
- Fix a cosmetic issue with clearing the screen on reconnection
- Fix a cosmetic issue with not clearing the userlist on certain types of network error
- Fix a cosmetic issue with closed PM tabs reappearing in some cases
- Fix a cosmetic issue with marking all PM tabs as read when switching to a single one
2016-11-29 1.0.2
- Rebuild with libnmdc 0.11
- Fix an issue with not setting a version in the client tag
2016-10-08 1.0.1
- Fix an issue with backward compatibility with `dcwebui2` configuration file format
2016-10-08 1.0.0
- Port `dcwebui2` from Node.js (Javascript) to Go
- Fix a cosmetic issue with not clearing userlist on disconnection

281
bindata.go Normal file

File diff suppressed because one or more lines are too long

184
build.sh
View File

@ -1,184 +0,0 @@
#!/bin/bash
# godist.sh is a template build script for golang applications.
#
# Copyright (c) 2016, The godist.sh Author(s)
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
set -eu
DIST_DIR=./_dist
EXTRA_FILES=(
nmdc-webfrontend.conf.SAMPLE
)
get_package_name() {
# Binaries are named after the containing folder.
pwd | tr / $'\n' | tail -n1
}
check_dependencies() {
# `set -e` will take care of killing the application if the dependency
# goes unresolved.
which 7z > /dev/null
which php > /dev/null
}
get_platform_description() {
if [[ $GOOS == windows ]] ; then
echo -n "win"
else
echo -n "${GOOS}"
fi
if [[ $GOARCH == 386 ]] ; then
echo -n 32
elif [[ $GOARCH == amd64 ]] ; then
echo -n 64
else
echo -n "-${GOARCH}"
if [[ -v $GOARM ]] ; then
echo -n "v${GOARM}"
fi
fi
}
pathfix() {
if [[ $(uname -o) == Cygwin ]] ; then
cygpath -w "$1"
else
echo "$1"
fi
}
single_build() {
local version="$1"
# Determine build output name
local bin_name=''
if [[ $GOOS == windows ]] ; then
local_bin_name="$(get_package_name).exe"
else
local_bin_name="$(get_package_name)"
fi
if [[ -f ./$local_bin_name ]] ; then
rm -f "./$local_bin_name"
fi
local tmpdir=$(mktemp -d)
# Platform identifier
local platform="$(get_platform_description)"
echo "[INFO] Building ${version} for ${platform}..."
# Build.
# GOARCH/GOOS supplied in function env
go build \
-a \
-ldflags '-s -w' \
-gcflags "-trimpath=${GOPATH}" \
-asmflags "-trimpath=${GOPATH}" \
-o "$(pathfix "${tmpdir}/${local_bin_name}")"
# Archive.
if [[ ! -d $DIST_DIR ]] ; then
mkdir "$DIST_DIR"
fi
local archive_name="${DIST_DIR}/$(get_package_name)-${version}-${platform}"
if [[ $GOOS == windows ]] ; then
archive_name="${archive_name}.7z"
7z a -mx9 "$archive_name" "${tmpdir}/${local_bin_name}" "${EXTRA_FILES[@]}" >/dev/null
else
archive_name="${archive_name}.tar.xz"
XZ_OPT='-9' tar caf "$archive_name" -C "${tmpdir}" "${local_bin_name}" -C "$(pwd)" "${EXTRA_FILES[@]}" --owner=0 --group=0 >/dev/null
fi
# Cleanup
rm -f "${tmpdir}/${local_bin_name}"
rmdir "$tmpdir"
}
datestamp() {
( TZ=UTC date +%Y%m%d%H%M%SZ )
}
usage() {
cat <<EOD
Usage: ./godist.sh [-v VERSION]
EOD
exit 0
}
main() {
check_dependencies
if [[ ! -v GOPATH ]] ; then
echo "Please set GOPATH." >&2
exit 1
fi
if [[ ! -d $GOPATH ]] ; then
echo "Invalid GOPATH set." >&2
exit 1
fi
local version=""
while getopts ':v:' flag ; do
case "$flag" in
v)
version="$OPTARG"
;;
*)
usage
;;
esac
done
if [[ $version == "" ]] ; then
read -p "Enter version string (blank for timestamp)> " version
if [[ $version == "" ]] ; then
version=$(datestamp)
fi
fi
if [[ -f ./bindata.go ]] ; then
rm ./bindata.go
fi
php clientpack.php
go-bindata -nomemcopy -prefix clientpack clientpack
GOARCH=amd64 GOOS=windows single_build "$version"
GOARCH=386 GOOS=windows single_build "$version"
GOARCH=amd64 GOOS=linux single_build "$version"
GOARCH=386 GOOS=linux single_build "$version"
# Also make source tarball
local SOURCE_FILES=(
client/
build.sh
clientpack.php
Config.go
main.go
nmdc-webfrontend.conf.SAMPLE
)
XZ_OPT='-9' tar caf "_dist/$(get_package_name)-${version}-src.tar.xz" "${SOURCE_FILES[@]}" --owner=0 --group=0 >/dev/null
echo "[INFO] Build complete."
}
main "$@"

View File

@ -71,10 +71,15 @@ html,body {
color:black;
font-weight:bold;
line-height:18px;
line-height:0;
height:18px;
width:18px;
text-align:center;
}
.menubutton svg {
width: 18px;
height: 18px;
}
.menubutton:hover {
background:white;
}
@ -397,4 +402,4 @@ html,body {
.position-panel {
display:block;
}
}
}

View File

@ -1,28 +1,27 @@
/* dcwebui.js */
//IIFEMODE:;(function() {
"use strict";
var SENTINEL_PASSWORD = "************";
var CHAT_SCROLLBACK_LIMIT = 50; // Once over 2x $limit, the first $limit will be trimmed off the list
var CHAT_SCROLLBACK_LIMIT = 200; // Once over 2x $limit, the first $limit will be trimmed off the list
var EXTERN_ROOT = window.location.protocol + "//" + window.location.host + "/";
var $ = (document.querySelectorAll ?
function(s) {
var r = document.querySelectorAll(s);
return (s[0] === '#' && r.length === 1) ? r[0] : r;
} :
function(s) {
// i'm not writing a selector engine...
if (! s.length) return [];
if (s[0] === '#') {
return document.getElementById(s.slice(1));
} else if (s[0] === '.') {
return document.getElementsByClassName(s.slice(1));
} else {
return document.getElementsByTagName(s);
}
var el = function(s) {
// There used to be a querySelectorAll implementation, but, better that we don't have
// potentially-incompatible implementations if this one does actually work.
// i'm not writing a selector engine...
if (! s.length) {
return [];
}
);
if (s[0] === '#') {
return document.getElementById(s.slice(1)); // single element
} else if (s[0] === '.') {
return document.getElementsByClassName(s.slice(1)); // multiple elements
} else {
return document.getElementsByTagName(s); // multiple elements
}
};
var nmdc_escape = function(str) {
return (
@ -46,8 +45,8 @@ var fmtBytes = function(b) {
var k = 1024;
var sizes = [' B', ' KiB', ' MiB', ' GiB', ' TiB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(3)) + sizes[i];
var i = Math.floor(Math.log(b) / Math.log(k));
return parseFloat((b / Math.pow(k, i)).toFixed(3)) + sizes[i];
};
@ -95,7 +94,7 @@ var b64 = function(str) {
})).replace(/=/g, '');
}
// https://gist.github.com/eligrey/1276030
// @ref https://gist.github.com/eligrey/1276030
var appendInnerHTML = function($el, html) {
var child = document.createElement("span");
child.innerHTML = html;
@ -106,7 +105,7 @@ var appendInnerHTML = function($el, html) {
}
};
// http://stackoverflow.com/a/5598797
// @ref http://stackoverflow.com/a/5598797
function getOffsetLeft( elem ) {
var offsetLeft = 0;
do {
@ -146,17 +145,23 @@ var date_format = function(d, format) {
/* */
var notify = function(title, body) {
var notify = function(title, body, tab) {
if (!("Notification" in window)) {
return; // not supported by browser
}
switch (window.Notification.permission) {
case "granted": {
new Notification(title, {
var n = new Notification(title, {
body: body,
icon: DCWEBUI_CONF.extern + "/favicon.ico"
icon: EXTERN_ROOT + "/favicon.ico"
});
n.onclick = function() {
parent.focus(); // recent chrome
window.focus(); // older browsers
tab_set(tab);
this.close();
};
} break;
case "denied": return;
@ -173,7 +178,7 @@ var notify = function(title, body) {
/* Tab writers */
var write = function(tab) {
var $tab = $('#inner-'+tab);
var $tab = el('#inner-'+tab);
return {
'cls': function() {
$tab.innerHTML = '';
@ -242,15 +247,14 @@ var userMenu = function(u, ev) {
usermenu.show();
//
ev.preventDefault();
return false;
return noprop(ev);
};
var userlist = {
'add': function(u) {
if (this.has(u)) return;
var userlists = $(".userlist");
var userlists = el(".userlist");
for (var l = 0, e = userlists.length; l !== e; ++l) {
var userlist = userlists[l];
@ -279,7 +283,7 @@ var userlist = {
return this;
},
'del': function(u) {
var userlists = $(".userlist");
var userlists = el(".userlist");
for (var l = 0, e = userlists.length; l !== e; ++l) {
if (! userlists[l].children) continue;
var userlist = userlists[l];
@ -296,7 +300,7 @@ var userlist = {
return this;
},
'clear': function() {
var userlists = $(".userlist");
var userlists = el(".userlist");
for (var i in userlists) {
if (! userlists[i].children) continue;
var userlist = userlists[i];
@ -309,7 +313,7 @@ var userlist = {
return this;
},
'names': function() {
var userlist = $(".userlist")[0].children;
var userlist = el(".userlist")[0].children;
var ret = [];
for (var i = 0, e = userlist.length; i < e; ++i) {
ret.push( textContent(userlist[i]) );
@ -317,14 +321,14 @@ var userlist = {
return ret;
},
'has': function(u) {
return $(".user-" + b64(u)).length !== 0; /* there are two - large and non-large */
return el(".user-" + b64(u)).length !== 0; /* there are two - large and non-large */
},
'count': function() {
return $(".userlist")[0].children.length;
return el(".userlist")[0].children.length;
},
'setInfo': function(nick, props) {
var baseClass = "user-" + b64(nick);
var $el = $("." + baseClass);
var $el = el("." + baseClass);
var prop_str = [];
if (props.Description.length > 0) {
prop_str.push(props.Description);
@ -335,6 +339,9 @@ var userlist = {
if (props.ClientTag.length > 0) {
prop_str.push(props.ClientTag + " " + props.ClientVersion);
}
if (props.IPAddress.length > 0) {
prop_str.push(props.IPAddress);
}
prop_str.push("Sharing " + fmtBytes(props.ShareSize));
for (var i = 0; i < $el.length; ++i) {
@ -350,7 +357,7 @@ var userlist = {
};
var submit = function() {
var str = $("#chatbox").value;
var str = el("#chatbox").value;
if (! str.length) return;
if (hub_state === STATE_READY_FOR_LOGIN) {
@ -364,7 +371,7 @@ var submit = function() {
}
if (hub_pass === SENTINEL_PASSWORD) {
// Probably not a real password. Attempt to load a better one from the saved state
var cache = persistence_get("login");
var cache = persistence_get("login", "");
if (cache.indexOf(":") != -1) {
hub_pass = cache.substr(cache.indexOf(":") + 1);
}
@ -393,7 +400,7 @@ var submit = function() {
write("tab-main").system("Invalid internal state.");
}
$("#chatbox").value = '';
el("#chatbox").value = '';
};
/* page visibility */
@ -440,13 +447,13 @@ var pagevis_setup = function(fnActive, fnInactive) {
*/
var tab_set = function(tab) {
var tabs = $(".tabpane");
var tabs = el(".tabpane");
for (var i in tabs) {
try {
tabs[i].style.display = (tabs[i].id === tab ? 'block' : 'none');
} catch (e) {};
}
var tabitems = $(".tabitem");
var tabitems = el(".tabitem");
for (var i in tabitems) {
try {
// Update UNREAD/SELECTED flags for the target
@ -474,13 +481,13 @@ var tab_set = function(tab) {
updateTitle();
write(tab).scroll();
$("#chatbox").focus();
el("#chatbox").focus();
last_tab = tab;
};
var tab_new = function(id, name) {
appendInnerHTML($("#bar"),
appendInnerHTML(el("#bar"),
' <div class="tabitem" data-tab="tab-ext-'+id+'" id="tabitem-tab-ext-'+id+'">'+
'<span class="tab-label">'+
hesc(name)+
@ -488,7 +495,7 @@ var tab_new = function(id, name) {
'<a class="tab-closer" data-tab="tab-ext-'+id+'">&times;</a>'+
'</div> '
);
appendInnerHTML($("#extratabs"),
appendInnerHTML(el("#extratabs"),
' <div class="tabpane content placement-mid" id="tab-ext-'+id+'" style="display:none;">'+
'<div class="content-inner" id="inner-tab-ext-'+id+'"></div>'+
'</div>'
@ -501,11 +508,11 @@ var tab_free = function(id) {
if (id === "tab-main") return;
// remove tab item and body
var el = $("#tabitem-"+id);
el.parentNode.removeChild(el);
var $el = el("#tabitem-"+id);
$el.parentNode.removeChild($el);
var el = $("#"+id);
el.parentNode.removeChild(el);
$el = el("#"+id);
$el.parentNode.removeChild($el);
// clear from PM tabs
for (var i in pm_tabs) {
@ -524,38 +531,40 @@ var tab_free = function(id) {
}
};
var noprop = function(ev) {
if (ev.preventDefault) {
ev.preventDefault();
}
if (ev.stopPropagation) {
ev.stopPropagation();
} else {
ev.cancelBubble = true; // oldIE
}
return false;
}
var tab_addHandlers = function() {
var tabitems = $(".tabitem");
var tabitems = el(".tabitem");
for (var i = 0; i < tabitems.length; i++) {
if (! tabitems[i]) continue;
tabitems[i].onclick = function(ev) {
tab_set( this.getAttribute('data-tab') );
// 360nobubble
if (ev.stopPropagation) {
ev.stopPropagation();
} else {
ev.cancelBubble = true; // oldIE
}
return false;
return noprop(ev);
};
}
var tabclosers = $(".tab-closer");
var tabclosers = el(".tab-closer");
for (var i = 0; i < tabclosers.length; i++) {
if (! tabclosers[i]) continue;
tabclosers[i].onclick = function(ev) {
tab_free( this.getAttribute('data-tab') );
// 360nobubble
if (ev.stopPropagation) {
ev.stopPropagation();
} else {
ev.cancelBubble = true; // oldIE
}
return false;
return noprop(ev);
};
}
};
@ -588,7 +597,7 @@ var tabcomplete_state = '';
var tabcompletion_start = function(direction) {
var cursor = $("#chatbox").value.replace(/^.*\s([^\s]+)$/, '$1');
var cursor = el("#chatbox").value.replace(/^.*\s([^\s]+)$/, '$1');
if (tabcomplete_state === '') {
// new tab completion
@ -624,10 +633,10 @@ var tabcompletion_start = function(direction) {
// Replace in textbox
var chatprefix = $("#chatbox").value.substr(0, $("#chatbox").value.length - cursor.length);
var chatprefix = el("#chatbox").value.substr(0, el("#chatbox").value.length - cursor.length);
$("#chatbox").value = chatprefix + targetName;
$("#chatbox").focus();
el("#chatbox").value = chatprefix + targetName;
el("#chatbox").focus();
};
var tabcompletion_inactive = function() {
@ -676,10 +685,17 @@ MenuList.prototype.toggle = function() {
/* */
var menu = new MenuList($("#menubutton"));
var menu = new MenuList(el("#menubutton"));
menu.reset = function() {
this.clear();
if (contented_url.length > 0) {
menu.add("Upload", function() {
contented_load();
});
}
this.add(joinparts_getstr(), toggle_joinparts);
this.add(desktop_notifications_fmtstr(), desktop_notifications_toggle);
this.add(warnonclose_fmtstr(), warnonclose_toggle);
@ -772,20 +788,24 @@ var toggle_joinparts = function(ev) {
var updateTitle = function() {
var prefix = "";
var unrTabs = $(".unread");
var unrTabs = el(".unread");
if (unrTabs.length === 1 && unrTabs[0].getAttribute('data-tab') == "tab-main") {
prefix = "[" + mainchat_unread_count + " NEW] "
} else if (unrTabs.length > 0) {
prefix = "[NEW PM] "
}
document.title = prefix + hub_hubname + "("+userlist.count()+") "
var suffix = "";
if (userlist.count() > 0) {
suffix = " ("+userlist.count()+")";
}
document.title = prefix + hub_hubname + suffix;
};
var sock = {};
var hub_state = 0; // [disconnected, sent-nick, connected]
var hub_last_nick = '';
var hub_hubname = DCWEBUI_CONF.title;
var hub_hubname = "Loading...";
var pm_tabs = {}; // nick => tabid
var next_tabid = 1;
@ -847,7 +867,7 @@ var desktop_notifications_toggle = function(ev) {
persistence_set("notifications", desktop_notifications_enabled);
if (desktop_notifications_enabled) {
desktop_notifications_onEnable();
notify(hub_hubname, "Desktop popups enabled", "tab-main");
}
persistence_set("popups", desktop_notifications_enabled);
@ -855,10 +875,6 @@ var desktop_notifications_toggle = function(ev) {
$el.innerHTML = desktop_notifications_fmtstr();
};
var desktop_notifications_onEnable = function() {
notify(hub_hubname, "Desktop popups enabled");
}
var scrollback_move = function(delta) {
if (chat_scrollback.length === 0) {
return; // no effect
@ -877,7 +893,7 @@ var scrollback_move = function(delta) {
}
}
$("#chatbox").value = chat_scrollback[chat_scrollback_index];
el("#chatbox").value = chat_scrollback[chat_scrollback_index];
};
/* */
@ -902,24 +918,25 @@ var transition = function(new_state) {
switch(new_state) {
case STATE_DISCONNECTED: {
userlist.clear();
$("#chatbox").disabled = true;
$("#chatbox").value = ''; // clear
el("#chatbox").disabled = true;
el("#chatbox").value = ''; // clear
} break;
case STATE_READY_FOR_LOGIN: {
userlist.clear();
$("#chatbox").spellcheck = false;
$("#chatbox").disabled = false;
$("#chatbox").value = ''; // clear
el("#chatbox").spellcheck = false;
el("#chatbox").disabled = false;
el("#chatbox").value = ''; // clear
} break;
case STATE_CONNECTING: {
$("#chatbox").disabled = true;
el("#chatbox").disabled = true;
} break;
case STATE_ACTIVE: {
$("#chatbox").disabled = false;
$("#chatbox").spellcheck = true;
case STATE_ACTIVE: {
write("tab-main").system("Now talking on "+hub_hubname);
el("#chatbox").disabled = false;
el("#chatbox").spellcheck = true;
} break;
}
};
@ -929,44 +946,76 @@ var tab_is_visible = function(tabref) {
}
var tab_mark_unread = function(tabref) {
if ($("#tabitem-"+tabref).className.indexOf('unread') === -1) {
$("#tabitem-"+tabref).className += " unread";
if (el("#tabitem-"+tabref).className.indexOf('unread') === -1) {
el("#tabitem-"+tabref).className += " unread";
updateTitle();
}
}
//
var contented_url = "";
var contented_loaded_sdk = false;
var contented_load = function() {
if (contented_url.length === 0) {
return;
}
var onceSDKLoaded = function() {
contented.init("#inner-tab-main", function(items) {
var val = el("#chatbox").value;
for (var i = 0; i < items.length; ++i) {
if (val.length > 0) {
val += " ";
}
val += contented.getPreviewURL(items[i]);
}
el("#chatbox").value = val;
});
};
if (contented_loaded_sdk) {
onceSDKLoaded();
} else {
var scriptElement = document.createElement('script');
scriptElement.onload = function() {
contented_loaded_sdk = true;
onceSDKLoaded();
};
scriptElement.src = contented_url + "sdk.js";
document.body.appendChild(scriptElement);
}
};
//
window.onload = function() {
write("tab-main").system("Communicating with server...");
show_joins = persistence_get("show_joins", false);
document.title = DCWEBUI_CONF.title;
document.title = hub_hubname; // "Loading...";
// HTML event handlers
$("#form-none").onsubmit = function(ev) {
el("#form-none").onsubmit = function(ev) {
submit();
// don't submit form
ev.preventDefault();
return false;
return noprop(ev); // don't submit form
};
$("#chatbox").onkeydown = function(ev) {
el("#chatbox").onkeydown = function(ev) {
if (ev.keyCode === 9 /* Tab */) {
tabcompletion_start( ev.shiftKey ? -1 : 1 );
ev.preventDefault();
return false;
return noprop(ev);
} else if (ev.keyCode == 38 /* ArrowUp */ && ev.ctrlKey) {
scrollback_move(-1);
ev.preventDefault();
return false;
return noprop(ev);
} else if (ev.keyCode == 40 /* ArrowDown */ && ev.ctrlKey) {
scrollback_move(1);
ev.preventDefault();
return false;
return noprop(ev);
} else {
tabcompletion_inactive();
@ -983,11 +1032,9 @@ window.onload = function() {
usermenu.hide();
};
$("#menubutton").onclick = function(ev) {
el("#menubutton").onclick = function(ev) {
menu.toggle();
ev.preventDefault();
ev.stopPropagation();
return false;
return noprop(ev);
};
window.onclick = function() {
@ -1007,13 +1054,9 @@ window.onload = function() {
timestamp_format_index = persistence_get("timestamps", 0);
should_warn_on_close = persistence_get("warnonclose");
should_warn_on_close = persistence_get("warnonclose", false);
desktop_notifications_enabled = persistence_get("popups");
if (desktop_notifications_enabled) {
// prompt for permissions
desktop_notifications_onEnable();
}
desktop_notifications_enabled = persistence_get("popups", false);
menu.reset();
@ -1039,7 +1082,7 @@ window.onload = function() {
// Socket event handlers
sock = io.connect(DCWEBUI_CONF.extern);
sock = io.connect(EXTERN_ROOT);
sock.on('cls', function() {
transition(STATE_READY_FOR_LOGIN);
@ -1047,7 +1090,7 @@ window.onload = function() {
if (pre_login.indexOf(":") !== -1) {
pre_login = pre_login.substr(0, pre_login.indexOf(":")) + ":" + SENTINEL_PASSWORD;
}
$("#chatbox").value = pre_login;
el("#chatbox").value = pre_login;
if (have_cleared_once) {
// re-log-in automatically
@ -1062,7 +1105,6 @@ window.onload = function() {
}
});
sock.on('hubname', function(s) {
write("tab-main").system("Now talking on "+s);
hub_hubname = s;
updateTitle();
});
@ -1089,7 +1131,7 @@ window.onload = function() {
tab_mark_unread( pm_tabs[data.user] );
if (desktop_notifications_enabled) {
notify("Message from " + data.user, data.message);
notify("Message from " + data.user, data.message, pm_tabs[data.user]);
}
}
});
@ -1142,6 +1184,8 @@ window.onload = function() {
sock.on('usercommand', function(data) {
process_usercommand(data);
});
sock.on('contented', function(url) {
contented_url = url;
menu.reset(); // sent before login
});
};
//IIFEMODE:})();

View File

@ -6,12 +6,16 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="stylesheet" href="/dcwebui.css">
<link rel="stylesheet" href="/dcwebui.css">
<title>Loading...</title>
</head>
<body>
<div class="tabbar placement-top" id="bar">
<div class="menubutton" id="menubutton">&#9776;</div>
<div class="menubutton" id="menubutton">
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" />
</svg>
</div>
<div class="tabitem selected" data-tab="tab-main" id="tabitem-tab-main">
<span class="tab-label">
Main
@ -53,7 +57,6 @@
</div>
<script type="text/javascript" src="/socket.io-1.7.2.js"></script>
<script type="text/javascript" src="/conf"></script>
<script type="text/javascript" src="/dcwebui.js"></script>
</body>
</html>
</html>

View File

@ -1,56 +0,0 @@
#!/usr/bin/php
<?php
# Dependencies:
# - PHP
# - Uglifyjs (`npm install -g uglifyjs`)
# - Lessc (`npm install -g less`)
# - Lessc minifier (`npm install -g less-plugin-clean-css`)
# - HTML minifier (`npm install -g html-minifier`)
echo "Compressing/minifying web resources...\n";
if (is_dir('clientpack')) {
`rm -r clientpack`;
}
`cp -r client clientpack`;
// Toggle IIFE on
`sed -i -re 's~//IIFEMODE:~~g' clientpack/dcwebui.js`;
// Minify JS
`uglifyjs clientpack/dcwebui.js -o clientpack/dcwebui.min.js -c -m`;
// Minify CSS
`lessc --clean-css clientpack/dcwebui.css clientpack/dcwebui.min.css`;
// Embed css into HTML file
$html_content = file_get_contents('clientpack/index.htm');
$html_content = preg_replace_callback('~<link[^>]+dcwebui.css[^>]*>~', function() { return '<style type="text/css">'.file_get_contents('clientpack/dcwebui.min.css').'</style>'; }, $html_content);
// Embed JS into HTML file
$html_content = preg_replace_callback('~<script[^>]+dcwebui.js[^>]*>~', function() { return '<script type="text/javascript">'.file_get_contents('clientpack/dcwebui.min.js').'</script>'; }, $html_content);
// Embed socketio into HTML file
define('SIO_NAME', 'socket.io-1.7.2.js');
$html_content = preg_replace_callback('~<script[^>]+'.SIO_NAME.'[^>]*>~', function() { return '<script type="text/javascript">'.file_get_contents('clientpack/'.SIO_NAME).'</script>'; }, $html_content);
// Minify the combined file
file_put_contents('clientpack/index.htm', $html_content);
`html-minifier --collapse-whitespace -o clientpack/index.min.htm clientpack/index.htm`;
// Clean up files
`rm clientpack/{index.htm,dcwebui{.min,}.js,dcwebui{.min,}.css}`;
unlink('clientpack/'.SIO_NAME);
rename('clientpack/index.min.htm', 'clientpack/index.htm');

9
go.mod Normal file
View File

@ -0,0 +1,9 @@
module code.ivysaur.me/nmdc-webfrontend
require (
code.ivysaur.me/libnmdc v0.16.0
github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7 // indirect
github.com/googollee/go-engine.io v0.0.0-20170224222511-80ae0e43aca1 // indirect
github.com/googollee/go-socket.io v0.0.0-20170525141029-5447e71f36d3
github.com/gorilla/websocket v1.2.0 // indirect
)

10
go.sum Normal file
View File

@ -0,0 +1,10 @@
code.ivysaur.me/libnmdc v0.16.0 h1:/y4Olm/DoZn83vE/QV1NyjQ2MF20opEJ+sxwDlDTIoE=
code.ivysaur.me/libnmdc v0.16.0/go.mod h1:ZHCjIX/zm29hd2H8YtzOBHiSyowZNOHvgoMRjKbBLLg=
github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7 h1:jBEtq1t2gpn2kEzvRlCUxvvrxl5aSWkXNPwe/hwvSNQ=
github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7/go.mod h1:ruCYvt9rtYymAr4rNmfYJrl1dz8HSXUFP7cufqKOsDI=
github.com/googollee/go-engine.io v0.0.0-20170224222511-80ae0e43aca1 h1:EYruZXoG1VD2n22p93st1miPfLNB7KiTuyA7qit4gnU=
github.com/googollee/go-engine.io v0.0.0-20170224222511-80ae0e43aca1/go.mod h1:MBpz1MS3P4HtRcBpQU4HcjvWXZ9q+JWacMEh2/BFYbg=
github.com/googollee/go-socket.io v0.0.0-20170525141029-5447e71f36d3 h1:J+S/ZG2A5qZPZM4+WwpOA3cc5tVL8OTKf2mWEO2dXN0=
github.com/googollee/go-socket.io v0.0.0-20170525141029-5447e71f36d3/go.mod h1:ftBGBMhSYToR5oV4ImIPKvAIsNaTkLC+tTvoNafqxlQ=
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=

100
main.go
View File

@ -5,13 +5,16 @@ import (
"fmt"
"io/ioutil"
"log"
"mime"
"net/http"
"strings"
"path/filepath"
"code.ivysaur.me/libnmdc"
"github.com/googollee/go-socket.io"
)
var VERSION string = `nmdc-webfrontend/devel-unreleased`
type App struct {
cfg *Config
}
@ -45,14 +48,19 @@ func (this *App) HubWorker(Nick, Pass string, so socketio.Socket, done chan stru
selfUser.ClientTag = this.cfg.Hub.Tag
selfUser.ClientVersion = libnmdc.DEFAULT_CLIENT_VERSION
hco := libnmdc.HubConnectionOptions{
Address: libnmdc.HubAddress(fmt.Sprintf("%s:%d", this.cfg.Hub.Address, this.cfg.Hub.Port)),
Self: *selfUser,
NickPassword: Pass,
NumEventsToBuffer: 0,
url := this.cfg.Hub.Address
if this.cfg.Hub.Port == 0 {
url = fmt.Sprintf("%s:%d", this.cfg.Hub.Address, this.cfg.Hub.Port)
}
hub := hco.Connect()
hco := libnmdc.HubConnectionOptions{
Address: libnmdc.HubAddress(url),
Self: selfUser,
NickPassword: Pass,
}
hubEvents := make(chan libnmdc.HubEvent, 10)
hub := libnmdc.ConnectAsync(&hco, hubEvents)
defer func() {
hub.Disconnect()
@ -102,7 +110,7 @@ func (this *App) HubWorker(Nick, Pass string, so socketio.Socket, done chan stru
for {
select {
case hev, ok := <-hub.OnEvent:
case hev, ok := <-hubEvents:
if !ok {
log.Printf("[%s] hub chan closed\n", so.Id())
return // abandon
@ -164,9 +172,14 @@ func (this *App) SocketIOServer(so socketio.Socket) {
log.Printf("[%s] Client connected", so.Id())
so.Emit("cls")
so.Emit("hubname", this.cfg.Web.Title)
so.Emit("raw", this.cfg.App.MotdHTML+"<br>")
so.Emit("sys", "Enter a name to connect as (or name:pass for a registered nick)")
if len(this.cfg.App.ContentedServer) > 0 {
so.Emit("contented", this.cfg.App.ContentedServer)
}
doneChan := make(chan struct{}, 0)
so.On("hello", func(data map[string]string) {
@ -185,24 +198,6 @@ func (this *App) customFaviconHandler(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "favicon.ico")
}
func (this *App) ConfigRequestHandler(w http.ResponseWriter, r *http.Request) {
confStruct := struct {
Extern string `json:"extern"`
Title string `json:"title"`
}{
Extern: this.cfg.Web.Extern,
Title: this.cfg.Web.Title,
}
confBytes, _ := json.Marshal(confStruct)
//
w.Header().Set("Content-Type", "text/javascript")
w.WriteHeader(200)
fmt.Fprintf(w, "var DCWEBUI_CONF = %s;\n", string(confBytes))
}
func (this *App) StaticRequestHandler(w http.ResponseWriter, r *http.Request) {
fileName := r.URL.Path[1:]
if fileName == "" {
@ -215,32 +210,16 @@ func (this *App) StaticRequestHandler(w http.ResponseWriter, r *http.Request) {
return
}
knownContentTypes := map[string]string{
".htm": "text/html",
".png": "image/png",
".ico": "image/x-icon",
// No CSS/JS since they're embedded in the HTML
}
foundMime := false
for ext, mimeType := range knownContentTypes {
if strings.HasSuffix(fileName, ext) {
w.Header().Set("Content-Type", mimeType)
foundMime = true
break
}
}
if !foundMime {
w.Header().Set("Content-Type", "application/x-octet-stream")
}
dataInfo, _ := AssetInfo(fileName)
w.Header().Set("Content-Length", fmt.Sprintf("%d", dataInfo.Size()))
w.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(fileName)))
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(data)))
w.Write(data)
}
func (this *App) RunServer() {
// Inner mux {{
innerMux := http.NewServeMux()
// Socket.io handler
server, err := socketio.NewServer(nil)
if err != nil {
@ -250,30 +229,39 @@ func (this *App) RunServer() {
server.On("error", func(so socketio.Socket, err error) {
log.Println("error:", err)
})
http.Handle("/socket.io/", server)
// Configuration handler
http.HandleFunc("/conf", this.ConfigRequestHandler)
innerMux.Handle("/socket.io/", server)
// Custom favicon handler
if this.cfg.Web.CustomFavicon {
http.HandleFunc("/favicon.ico", this.customFaviconHandler)
innerMux.HandleFunc("/favicon.ico", this.customFaviconHandler)
}
// Other files: asset handler
// Asset handler
if this.cfg.Web.ExternalWebroot {
http.Handle("/", http.FileServer(http.Dir("client")))
innerMux.Handle("/", http.FileServer(http.Dir("client")))
} else {
http.HandleFunc("/", this.StaticRequestHandler)
innerMux.HandleFunc("/", this.StaticRequestHandler)
}
// }}
// Wrapper mux {{
outerMux := http.NewServeMux()
outerMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", VERSION)
innerMux.ServeHTTP(w, r)
})
// }}
// Listen and serve
bindAddr := fmt.Sprintf("%s:%d", this.cfg.Web.BindTo, this.cfg.Web.Port)
log.Printf("Serving at %s...", bindAddr)
log.Fatal(http.ListenAndServe(bindAddr, nil))
log.Fatal(http.ListenAndServe(bindAddr, outerMux))
}
func main() {
log.Println(VERSION)
a, err := NewApp("nmdc-webfrontend.conf")
if err != nil {
log.Fatal(err.Error())

View File

@ -1,21 +1,19 @@
{
"app": {
"motd" : "Welcome!<br>"
"motd": "Welcome!<br>"
},
"web": {
"port" : 8082,
"port": 8082,
"bind_to": "127.0.0.1",
"extern" : "http://127.0.0.1:8082",
"title" : "DCWebUI",
"title": "NMDC Web Frontend",
"custom_favicon": false
},
"hub": {
"address": "127.0.0.1",
"port" : 411,
"tag" : "nmdc-webfrontend"
"address": "nmdc://127.0.0.1:411",
"tag": "nmdc-webfrontend"
}
}
}

14
vendor/code.ivysaur.me/libnmdc/.hgignore generated vendored Normal file
View File

@ -0,0 +1,14 @@
mode:regex
# Compilation output
\.(?:exe|a)$
^pkg/
# Dependencies
^src/(?:github.com|gopkg.in|golang.org)/
# Scratch space
^src/nmdc/
# Binary release artefacts
/?__dist/

20
vendor/code.ivysaur.me/libnmdc/.hgtags generated vendored Normal file
View File

@ -0,0 +1,20 @@
945ab4b16d05aa084f71bf5da9a3f687e0ec8bbd v0.1.0
02a360e95480b97ddad83add5db48b2766339a99 nmdc-log-service-1.0.0
137c1b65039e03c80379826a6efdfd808f6fbc8f v0.2.0
d8b64d5527c2a5e4d76872e5bc3d69f7646135c6 v0.3.0
fca41372e400853775b02e951f9db91d87f41adb nmdc-log-service-1.0.1
050b424a7c5d5a27c9323c8810f3afbead1f5b96 v0.4.0
da9f123633f9c28be6435ed7898139665d4c39d9 nmdc-log-service-1.0.2
75a78f6a78f249a2cd8aa3d29f7e5e6319b4e03b v0.5.0
4116422bb10229d887f9296970a166fa1ef8c5fd nmdc-log-service-1.0.3
cb86f3a40115cc46f450c0c83fd9b9d3b740e820 nmdc-log-service-1.0.4
cb86f3a40115cc46f450c0c83fd9b9d3b740e820 v0.6.0
71343a2c641a438206d30ea7e75dc89a11dbef00 v0.7.0
b0e57a5fcffdf4102d669db51a3648ddf66a0792 v0.8.0
e7c2c71ef24b386add728fad35fff4a996fccbac v0.9.0
3ecc037cf2d7080572fe87c2e39ecd153fb0e947 v0.10.0
5149ffe70ea8475e480b682345b31aa45a3352db v0.11.0
22b156a6fc2f6161765317f4ec9ab3731a26e0e2 v0.12.0
3ee0f4ea5142d66079a9500bdcd48a53bdcf362f v0.13.0
6422ed687cd308c339b6dc188bbe1034ed93f893 v0.14.0
84fb191007017862ffc37af68dcdace5d8c06eee v0.15.0

711
vendor/code.ivysaur.me/libnmdc/AdcProtocol.go generated vendored Normal file
View File

@ -0,0 +1,711 @@
package libnmdc
import (
"encoding/base32"
"fmt"
"regexp"
"strconv"
"strings"
)
type adcState int
const (
adcStateProtocol adcState = 0
adcStateIdentify adcState = 1
adcStateVerify adcState = 2
adcStateNormal adcState = 3
adcStateData adcState = 4
)
type AdcProtocol struct {
hc *HubConnection
state adcState
sid, pid, cid string // all in base32 encoding
supports map[string]struct{}
}
const (
// extra extensions that aren't flagged in SUPPORTS
adcSeparateApVe string = "SEPARATE_AP_VE" // we invented this string
)
func NewAdcProtocol(hc *HubConnection) Protocol {
proto := AdcProtocol{
hc: hc,
state: adcStateProtocol,
supports: make(map[string]struct{}),
}
rxPid := regexp.MustCompile("^[A-Z2-7]{39}$")
if !rxPid.MatchString(hc.Hco.AdcPID) {
hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Invalid custom PID, regenerating"})
hc.Hco.AdcPID = NewPID()
}
pid_base32 := hc.Hco.AdcPID
cid_base32, err := proto.pid2cid(pid_base32)
if err != nil {
panic(err)
}
proto.cid = cid_base32
proto.pid = pid_base32
// Start logging in
hc.SayRaw("HSUP ADBASE ADTIGR ADUCMD\n")
return &proto
}
func (this *AdcProtocol) pid2cid(pid_base32 string) (string, error) {
pid_raw, err := base32.StdEncoding.DecodeString(pid_base32 + "=")
if err != nil {
return "", err
}
cid_raw := Tiger(string(pid_raw))
cid_base32 := Base32(cid_raw)
return cid_base32, nil
}
func (this *AdcProtocol) SID2Nick(sid string) (string, bool) {
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
nick, ok := this.hc.userSIDs[sid]
return nick, ok
}
func (this *AdcProtocol) Nick2SID(targetNick string) (string, bool) {
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
for sid, nick := range this.hc.userSIDs {
if nick == targetNick {
return sid, true
}
}
return "", false
}
func (this *AdcProtocol) ProcessCommand(msg string) {
if len(msg) == 0 {
return
}
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: msg})
parts := strings.Split(msg, " ")
switch parts[0] {
case "ISUP":
if !(this.state == adcStateProtocol || this.state == adcStateNormal) {
this.malformed(parts)
return
}
for _, supportflag := range parts[1:] {
if len(supportflag) < 2 {
this.malformed(parts)
return
}
if supportflag[0:2] == "AD" {
this.supports[supportflag[2:]] = struct{}{}
} else if supportflag[0:2] == "RM" {
delete(this.supports, supportflag[2:])
} else {
this.malformed(parts)
return
}
}
if this.state == adcStateProtocol {
this.state = adcStateIdentify
}
case "ISID":
if this.state != adcStateIdentify {
this.malformed(parts)
return
}
this.sid = parts[1]
// State transition IDENTIFY --> VERIFY and send our own info
this.hc.SayRaw("BINF " + this.escape(this.sid) + " " + this.ourINFO(true) + "\n")
this.state = adcStateVerify
case "IINF":
// Hub telling information about itself
// ADCH++ sends this once we are successfully logged in
flags, err := this.parts2flags(parts[1:])
if err != nil {
this.logError(err)
return
}
if flags["CT"] != "32" {
this.malformed(parts)
return
}
err = this.handleHubInfo(flags)
if err != nil {
this.logError(err)
return
}
if this.state != adcStateNormal {
this.enterNormalState() // successful login
}
case "BINF":
if this.state != adcStateNormal {
this.enterNormalState() // successful login
}
sid := parts[1]
flags, err := this.parts2flags(parts[2:])
if err != nil {
this.logError(err)
return
}
// Log this user in, and associate this SID with this user
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
oldNick, sidExists := this.hc.userSIDs[sid]
uinfo := UserInfo{}
if sidExists {
uinfo_lookup, ok := this.hc.users[oldNick]
if !ok {
// Shouldn't happen
this.hc.processEvent(HubEvent{
EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN,
Message: fmt.Sprintf("Hub connection corrupted (missing info for SID='%s' nick='%s'), disconnecting", sid, oldNick),
})
this.hc.Disconnect()
return
}
uinfo = uinfo_lookup
}
this.updateUserInfo(&uinfo, flags)
newNick := uinfo.Nick
if len(newNick) == 0 {
this.logError(fmt.Errorf("Zero-length nick for user (SID='%s')", sid))
}
shouldHandleNewUser := false
if sidExists && oldNick != newNick {
// Nick change = delete all trace of this user first, treat as new
delete(this.hc.users, oldNick)
delete(this.hc.userSIDs, sid)
this.hc.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: oldNick})
shouldHandleNewUser = true
} else if sidExists && oldNick == newNick {
// Updating existing user
this.hc.users[newNick] = uinfo
this.hc.processEvent(HubEvent{EventType: EVENT_USER_UPDATED_INFO, Nick: newNick})
} else if !sidExists {
// User joined
shouldHandleNewUser = true
}
//
if shouldHandleNewUser {
// Install this SID as pointing to this nick
this.hc.userSIDs[sid] = uinfo.Nick
// Check if this nick was in use by any other SID already
for otherSid, otherSidNick := range this.hc.userSIDs {
if otherSidNick == newNick && otherSid != sid {
this.hc.processEvent(HubEvent{
EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN,
Message: fmt.Sprintf("Hub connection corrupted (duplicate SIDs '%s' and '%s' for nick '%s'), disconnecting", sid, otherSid, newNick),
})
this.hc.Disconnect()
return
}
}
// Notifications
this.hc.users[newNick] = uinfo
this.hc.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: newNick})
}
case "IMSG":
// General message from the hub
if len(parts) < 2 {
this.malformed(parts)
return
}
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: this.unescape(parts[1])})
case "ISTA":
// Error message from the hub
if len(parts) < 3 {
this.malformed(parts)
return
}
code, _ := strconv.Atoi(parts[1])
msg := this.unescape(parts[2])
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: this.ErrorMessage(code, msg)})
case "IQUI":
// Error message from the hub
// IQUI V3M6 DI1 MSNick\staken,\splease\spick\sanother\sone TL-1
if len(parts) < 2 {
this.malformed(parts)
return
}
sid := parts[1]
flags, err := this.parts2flags(parts[2:])
if err != nil {
return
}
if sid == this.sid {
if msg, ok := flags["MS"]; ok {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: "The hub is closing our connection because: " + this.unescape(msg)})
} else {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Message: "The hub is closing our connection"})
}
} else {
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
otherSidNick, ok := this.hc.userSIDs[sid]
if ok {
delete(this.hc.userSIDs, sid)
delete(this.hc.users, otherSidNick)
this.hc.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: otherSidNick})
} else {
// ??
this.logError(fmt.Errorf("An unknown user quit the hub (SID=%s)", sid))
}
}
case "BMSG":
// Message from a user
// BMSG ZVF4 hi
if len(parts) < 3 {
this.malformed(parts)
return
}
sid := this.unescape(parts[1])
msg := this.unescape(parts[2])
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
nick, ok := this.hc.userSIDs[sid]
if !ok {
this.logError(fmt.Errorf("Recieved message from unknown SID '%s'", sid))
return
}
this.hc.processEvent(HubEvent{EventType: EVENT_PUBLIC, Nick: nick, Message: msg})
case "IGPA":
// Password is needed
// IGPA 7EIAAAECLMAAAPJQAAADQQYAAAWAYAAAKVFQAAF6EAAAAAYFAAAA
// HPAS LZDIJOTZDPWHINHGPT5RHT6WLU7DRME7DQO2O3Q
if len(parts) < 2 {
this.malformed(parts)
return
}
/*
For GPA/PAS, assuming that '12345' is the random data supplied in GPA, then;
PAS = Base32( Hash( password + '12345' ) )
GPA: The data parameter is at least 24 random bytes (base32 encoded).
*/
data_base32 := parts[1]
if len(data_base32)%8 != 0 {
data_base32 += strings.Repeat("=", 8-(len(data_base32)%8))
}
data_raw, err := base32.StdEncoding.DecodeString(data_base32)
if err != nil {
this.logError(err)
return
}
resp := Base32(Tiger(this.hc.Hco.NickPassword + string(data_raw)))
this.hc.SayRaw("HPAS " + resp + "\n")
case "EMSG":
// Private message from other user
// EMSG I5RO FMWH test\spm PMI5RO
// EMSG sender recip==us message [flags...]
if len(parts) < 4 {
this.malformed(parts)
return
}
if parts[2] != this.sid {
this.logError(fmt.Errorf("Recieved a PM intended for someone else (got SID=%s expected SID=%s)", parts[2], this.sid))
return
}
senderSid := parts[1]
senderNick, ok := this.SID2Nick(parts[1])
if !ok {
this.logError(fmt.Errorf("Recieved a PM from an unknown user (SID=%s)", senderSid))
return
}
msg := this.unescape(parts[3])
this.hc.processEvent(HubEvent{EventType: EVENT_PRIVATE, Nick: senderNick, Message: msg})
case "ICMD":
// Usercommand
// ICMD ADCH++/About\sthis\shub TTHMSG\s+about\n CT3
if len(parts) < 2 {
this.malformed(parts)
return
}
uc := UserCommand{
Message: this.unescape(parts[1]),
Type: USERCOMMAND_TYPE_RAW, // default
}
flags, err := this.parts2flags(parts[2:])
if err != nil {
this.malformed(parts)
return
}
if ct, ok := flags["CT"]; ok {
ct64, _ := strconv.ParseUint(ct, 10, 64)
uc.Context = UserCommandContext(ct64)
}
if tt, ok := flags["TT"]; ok {
uc.Command = tt
}
if sp, ok := flags["SP"]; ok && sp == "1" {
uc.Type = USERCOMMAND_TYPE_SEPARATOR
}
if co, ok := flags["CO"]; ok && co == "1" {
uc.Type = USERCOMMAND_TYPE_NICKLIMITED // "Constrained" in ADC parlance
}
if rm, ok := flags["RM"]; ok && rm == "1" {
uc.RemoveThis = true
}
this.hc.processEvent(HubEvent{EventType: EVENT_USERCOMMAND, UserCommand: &uc})
// Ignored messages
// ````````````````
case "DCTM": // Client-client ConnectToMe
case "BSCH": // Search
default:
this.malformed(parts)
}
}
func (this *AdcProtocol) infoFlagsFor(u *UserInfo) map[string]string {
parts := map[string]string{
"NI": u.Nick,
"SS": fmt.Sprintf("%d", u.ShareSize),
"SF": fmt.Sprintf("%d", u.SharedFiles),
"US": fmt.Sprintf("%d", u.UploadSpeedBps),
"DS": fmt.Sprintf("%d", u.DownloadSpeedBps),
"SL": fmt.Sprintf("%d", u.Slots),
"HN": fmt.Sprintf("%d", u.HubsUnregistered),
"HR": fmt.Sprintf("%d", u.HubsRegistered),
"HO": fmt.Sprintf("%d", u.HubsOperator),
}
if _, ok := this.supports[adcSeparateApVe]; ok {
parts["AP"] = u.ClientTag
parts["VE"] = u.ClientVersion
} else {
parts["VE"] = fmt.Sprintf("%s %s", u.ClientTag, u.ClientVersion)
}
// Do not send the hub a CT (it decides what type we are)
return parts
}
func (this *AdcProtocol) ourINFO(includePid bool) string {
parts := this.infoFlagsFor(this.hc.Hco.Self)
parts["ID"] = this.cid
if includePid {
parts["PD"] = this.pid
}
ret := ""
for k, v := range parts {
ret += " " + k + this.escape(v)
}
return ret[1:]
}
func (this *AdcProtocol) parts2flags(parts []string) (map[string]string, error) {
flags := make(map[string]string, len(parts))
for _, flag := range parts {
if len(flag) < 2 {
return nil, fmt.Errorf("Malformed flag '%s'", flag)
}
flags[flag[0:2]] = this.unescape(flag[2:])
}
return flags, nil
}
func (this *AdcProtocol) handleHubInfo(flags map[string]string) error {
if flags["CT"] != "32" {
return fmt.Errorf("Expected CT==32")
}
// IINF DEADCH++\sTest\shub VE2.12.1\s(r"[unknown]")\sRelease HI1 NIADCH++ APADCH++ CT32
// AP: extension 3.24 "Application and version separation in INF"
// HI:
// Hub properties updated
// Special SUPPORT that is only indicated in IINF
if _, ok := flags["AP"]; ok {
this.supports[adcSeparateApVe] = struct{}{}
}
// Hub's name is in "NI", hub description in "DE"
hubName, ok := flags["NI"]
if ok {
if hubDesc, ok := flags["DE"]; ok && len(hubDesc) > 0 {
hubName += " - " + hubDesc
}
this.hc.HubName = hubName
this.hc.processEvent(HubEvent{EventType: EVENT_HUBNAME_CHANGED, Nick: this.hc.HubName})
}
return nil
}
func (this *AdcProtocol) updateUserInfo(u *UserInfo, flags map[string]string) {
// User MyINFO
// BINF GUPR IDFEARIFD33NTGC4YBEZ3UFQS5R4ZXXTFL2QN2GRY PDZMIFLG5EKZG3BDRRMIJPG7ARNA6KW3JVIH3DF7Q NIivysaur5 SL3 FS3 SS0 SF0 HN1 HR0 HO0 VEEiskaltDC++\s2.2.9 US2621440 KPSHA256/3UPRORG4BLJ4CG6TO6R3G75A67LXOGD437NALQALRWJF6XBOECTA I40.0.0.0 U418301
// BINF GUPR I4172.17.0.1 U418301 IDFEARIFD33NTGC4YBEZ3UFQS5R4ZXXTFL2QN2GRY VEEiskaltDC++\s2.2.9 SF0 NIivysaur5 SL3 HN1 HO0 KPSHA256/3UPRORG4BLJ4CG6TO6R3G75A67LXOGD437NALQALRWJF6XBOECTA HR0 FS3 SS0 US2621440 SUSEGA,ADC0,TCP4,UDP4
// Or maybe only incremental:
// BINF Z3BA HO1
// TODO
for prop, val := range flags {
switch prop {
case "ID":
u.CID = val
case "PD":
// ignore PID - it will only appear if we're talking about our own user
case "NI":
u.Nick = val
case "SL":
u.Slots, _ = strconv.ParseUint(val, 10, 64)
case "SS":
u.ShareSize, _ = strconv.ParseUint(val, 10, 64)
case "SF":
u.SharedFiles, _ = strconv.ParseUint(val, 10, 64)
case "HN":
u.HubsUnregistered, _ = strconv.ParseUint(val, 10, 64)
case "HR":
u.HubsRegistered, _ = strconv.ParseUint(val, 10, 64)
case "HO":
u.HubsOperator, _ = strconv.ParseUint(val, 10, 64)
case "US":
u.UploadSpeedBps, _ = strconv.ParseUint(val, 10, 64)
case "DS":
u.DownloadSpeedBps, _ = strconv.ParseUint(val, 10, 64)
case "KP":
u.Keyprint = val
case "I4":
u.IPv4Address = val
case "I6":
u.IPv6Address = val
case "U4":
u.IPv4UDPPort, _ = strconv.ParseUint(val, 10, 64)
case "U6":
u.IPv6UDPPort, _ = strconv.ParseUint(val, 10, 64)
case "SU":
u.SupportFlags = make(map[string]struct{})
for _, supportFlag := range strings.Split(val, ",") {
u.SupportFlags[supportFlag] = struct{}{}
}
}
}
// VE / AP
AP, hasAP := flags["AP"]
VE, hasVE := flags["VE"]
if hasAP && hasVE {
u.ClientTag = AP
u.ClientVersion = VE
} else if hasAP && !hasVE {
u.ClientTag, u.ClientVersion = this.getAPVEFromSingle(AP)
} else if !hasAP && hasVE {
u.ClientTag, u.ClientVersion = this.getAPVEFromSingle(VE)
}
}
func (this *AdcProtocol) getAPVEFromSingle(term string) (string, string) {
words := strings.Split(term, " ")
if len(words) > 1 {
return strings.Join(words[0:len(words)-1], " "), words[len(words)-1]
} else {
return term, "0"
}
}
func (this *AdcProtocol) enterNormalState() {
this.state = adcStateNormal
this.hc.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED})
this.hc.State = CONNECTIONSTATE_CONNECTED
}
func (this *AdcProtocol) malformed(parts []string) {
this.logError(fmt.Errorf("Ignoring malformed, unhandled, or out-of-state protocol command %v", parts))
}
func (this *AdcProtocol) logError(e error) {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Protocol error: " + e.Error()})
}
func (this *AdcProtocol) escape(plaintext string) string {
// The string "\s" escapes space, "\n" newline and "\\" backslash. This version of the protocol reserves all other escapes for future use; any message containing unknown escapes must be discarded.
v1 := strings.Replace(plaintext, `\`, `\\`, -1)
v2 := strings.Replace(v1, "\n", `\n`, -1)
return strings.Replace(v2, " ", `\s`, -1)
}
func (this *AdcProtocol) unescape(encoded string) string {
v1 := strings.Replace(encoded, `\s`, " ", -1)
v2 := strings.Replace(v1, `\n`, "\n", -1)
return strings.Replace(v2, `\\`, `\`, -1)
}
func (this *AdcProtocol) SayPublic(msg string) {
this.hc.SayRaw("BMSG " + this.sid + " " + this.escape(msg) + "\n")
}
func (this *AdcProtocol) SayPrivate(user, message string) {
if sid, ok := this.Nick2SID(user); ok {
this.hc.SayRaw("DMSG " + this.sid + " " + sid + " " + this.escape(message) + "\n")
} else {
this.logError(fmt.Errorf("Unknown user '%s'", user))
}
}
func (this *AdcProtocol) ProtoMessageSeparator() string {
return "\n"
}
func (this *AdcProtocol) ErrorMessage(code int, msg string) string {
severity := code / 100
category := (code % 100) / 10
cat_sub := (code % 100)
formatSeverity := func(severity int) string {
switch severity {
case 0:
return "OK"
case 1:
return "Warning"
case 2:
return "Error"
default:
return ""
}
}
formatCategory := func(category int) string {
switch category {
case 0:
return ""
case 1:
return "Hub not accepting users"
case 2:
return "Login failed"
case 3:
return "Access denied"
case 4:
return "Protocol error"
case 5:
return "Transfer error"
default:
return ""
}
}
formatCatSub := func(cat_sub int) string {
switch cat_sub {
case 11:
return "Hub is full"
case 12:
return "Hub is disabled"
case 21:
return "Invalid nick"
case 22:
return "Nick is already in use"
case 23:
return "Invalid password"
case 24:
return "CID already connected"
case 25:
return "Access denied"
case 26:
return "Registered users only"
case 27:
return "Invalid PID"
case 31:
return "Permanently banned"
case 32:
return "Temporarily banned"
default:
return ""
}
}
parts := make([]string, 0, 4)
if fs := formatSeverity(severity); len(fs) > 0 {
parts = append(parts, fs)
}
if fc := formatCategory(category); len(fc) > 0 {
parts = append(parts, fc)
}
if fcs := formatCatSub(cat_sub); len(fcs) > 0 {
parts = append(parts, fcs)
}
if len(msg) > 0 {
parts = append(parts, msg)
}
return strings.Join(parts, ": ") + fmt.Sprintf(" (code %d)", code)
}

80
vendor/code.ivysaur.me/libnmdc/AutodetectProtocol.go generated vendored Normal file
View File

@ -0,0 +1,80 @@
package libnmdc
import (
"sync"
"time"
)
type AutodetectProtocol struct {
hc *HubConnection
realProtoMut sync.Mutex
realProto Protocol
}
func NewAutodetectProtocol(hc *HubConnection) Protocol {
proto := AutodetectProtocol{
hc: hc,
realProto: nil,
}
go proto.timeout()
return &proto
}
func (this *AutodetectProtocol) timeout() {
time.Sleep(AUTODETECT_ADC_NMDC_TIMEOUT)
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewAdcProtocol(this.hc)
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Detected ADC protocol"})
}
}
func (this *AutodetectProtocol) ProcessCommand(msg string) {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
// We actually got some data using $ as the separator?
// Upgrade to a full NMDC protocol
this.realProto = NewNmdcProtocol(this.hc)
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Detected NMDC protocol"})
}
this.realProto.ProcessCommand(msg)
}
func (this *AutodetectProtocol) SayPublic(msg string) {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewNmdcProtocol(this.hc)
}
this.realProto.SayPublic(msg)
}
func (this *AutodetectProtocol) SayPrivate(user, message string) {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewNmdcProtocol(this.hc)
}
this.realProto.SayPrivate(user, message)
}
func (this *AutodetectProtocol) ProtoMessageSeparator() string {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
return "|"
}
return this.realProto.ProtoMessageSeparator()
}

29
vendor/code.ivysaur.me/libnmdc/ConnectionMode.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
package libnmdc
import (
"fmt"
)
type ConnectionMode rune
const (
CONNECTIONMODE_ACTIVE ConnectionMode = 'A' // 65
CONNECTIONMODE_PASSIVE ConnectionMode = 'P' // 49
CONNECTIONMODE_SOCKS5 ConnectionMode = '5' // 53
)
func (this ConnectionMode) String() string {
switch this {
case CONNECTIONMODE_ACTIVE:
return "Active"
case CONNECTIONMODE_PASSIVE:
return "Passive"
case CONNECTIONMODE_SOCKS5:
return "SOCKS5"
default:
return fmt.Sprintf("ConnectionMode(\"%s\")", string(this))
}
}

40
vendor/code.ivysaur.me/libnmdc/ConnectionState.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
package libnmdc
import (
"net"
)
type ConnectionState int
const (
CONNECTIONSTATE_DISCONNECTED = 1
CONNECTIONSTATE_CONNECTING = 2 // Handshake in progress
CONNECTIONSTATE_CONNECTED = 3
)
func (cs ConnectionState) String() string {
switch cs {
case CONNECTIONSTATE_DISCONNECTED:
return "Disconnected"
case CONNECTIONSTATE_CONNECTING:
return "Connecting"
case CONNECTIONSTATE_CONNECTED:
return "Connected"
default:
return "?"
}
}
func checkIsNetTimeout(err error) bool {
if err == nil {
return false
}
switch err.(type) {
case net.Error:
return err.(net.Error).Timeout()
default:
return false
}
}

15
vendor/code.ivysaur.me/libnmdc/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,15 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/cxmcc/tiger"
packages = ["."]
revision = "bde35e2713d7f674987c2ecb21a6b0fc33749516"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "c88ee670a5600b482019325b6d6633bb6b5fe789596dc29ef809aa7bb013927b"
solver-name = "gps-cdcl"
solver-version = 1

26
vendor/code.ivysaur.me/libnmdc/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,26 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
branch = "master"
name = "github.com/cxmcc/tiger"

48
vendor/code.ivysaur.me/libnmdc/HubAddress.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
package libnmdc
import (
"net/url"
"strings"
)
type HubAddress string
func (this *HubAddress) parse() url.URL {
parsed, err := url.Parse(strings.ToLower(string(*this)))
if err != nil || len(parsed.Host) == 0 {
parsed = &url.URL{
Scheme: "",
Host: string(*this),
}
}
// Add default port if not specified
if !strings.ContainsRune(parsed.Host, ':') {
parsed.Host = parsed.Host + ":411"
}
return *parsed
}
func (this *HubAddress) IsSecure() bool {
parsed := this.parse()
return parsed.Scheme == "nmdcs" || parsed.Scheme == "dchubs" || parsed.Scheme == "adcs"
}
func (this *HubAddress) GetHostOnly() string {
return this.parse().Host
}
func (this *HubAddress) GetProtocol() HubProtocol {
parsed := this.parse()
switch parsed.Scheme {
case "nmdc", "dchub", "nmdcs", "dchubs":
return HubProtocolNmdc
case "adc", "adcs":
return HubProtocolAdc
default:
return HubProtocolAutodetect
}
}

229
vendor/code.ivysaur.me/libnmdc/HubConnection.go generated vendored Normal file
View File

@ -0,0 +1,229 @@
package libnmdc
import (
"crypto/tls"
"fmt"
"net"
"regexp"
"sync"
"time"
)
type HubConnection struct {
// Supplied parameters
Hco *HubConnectionOptions
// Current remote status
HubName string
State ConnectionState
usersMut sync.RWMutex
users map[string]UserInfo
userSIDs map[string]string
proto Protocol
// Event callback
processEvent func(HubEvent)
// Private state
conn net.Conn // this is an interface
connValid bool
autoReconnect bool
lastDataRecieved time.Time
}
// Thread-safe user accessor.
func (this *HubConnection) Users(cb func(*map[string]UserInfo) error) error {
this.usersMut.Lock()
defer this.usersMut.Unlock()
return cb(&this.users)
}
func (this *HubConnection) SayPublic(message string) {
this.proto.SayPublic(message)
}
func (this *HubConnection) SayPrivate(recipient string, message string) {
this.proto.SayPrivate(recipient, message)
}
func (this *HubConnection) UserExists(nick string) bool {
this.usersMut.RLock()
defer this.usersMut.RUnlock()
_, already_existed := this.users[nick]
return already_existed
}
func (this *HubConnection) UserCount() int {
this.usersMut.RLock()
defer this.usersMut.RUnlock()
return len(this.users)
}
func (this *HubConnection) userJoined_NameOnly(nick string) {
if !this.UserExists(nick) {
this.usersMut.Lock()
this.users[nick] = *NewUserInfo(nick)
this.usersMut.Unlock() // Don't lock over a processEvent boundary
this.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: nick})
}
}
func (this *HubConnection) userJoined_Full(uinf *UserInfo) {
// n.b. also called when we get a replacement MyINFO for someone
this.usersMut.Lock()
_, userExisted := this.users[uinf.Nick] // don't use UserExists as it would deadlock the mutex
this.users[uinf.Nick] = *uinf
this.usersMut.Unlock() // Don't lock over a processEvent boundary
if !userExisted {
this.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: uinf.Nick})
} else {
this.processEvent(HubEvent{EventType: EVENT_USER_UPDATED_INFO, Nick: uinf.Nick})
}
}
// SayRaw sends raw bytes over the TCP socket. Callers should add the protocol
// terminating character themselves (e.g. `|` for NMDC).
// Note that protocol messages are transmitted on the caller thread, not from
// any internal libnmdc thread.
func (this *HubConnection) SayRaw(protocolCommand string) error {
if !this.connValid {
return ErrNotConnected
}
_, err := this.conn.Write([]byte(protocolCommand))
return err
}
func (this *HubConnection) SayKeepalive() error {
if !this.connValid {
return ErrNotConnected
}
return this.SayRaw(this.proto.ProtoMessageSeparator())
}
func (this *HubConnection) Disconnect() {
this.autoReconnect = false
if this.conn != nil {
this.conn.Close()
}
// A CONNECTIONSTATE_DISCONNECTED message will be emitted by the worker.
}
func (this *HubConnection) worker() {
var fullBuffer string
var err error = nil
var nbytes int = 0
for {
// If we're not connected, attempt reconnect
if this.conn == nil {
fullBuffer = "" // clear
if this.Hco.Address.IsSecure() {
this.conn, err = tls.Dial("tcp", this.Hco.Address.GetHostOnly(), &tls.Config{
InsecureSkipVerify: this.Hco.SkipVerifyTLS,
})
} else {
this.conn, err = net.Dial("tcp", this.Hco.Address.GetHostOnly())
}
if err != nil {
this.State = CONNECTIONSTATE_DISCONNECTED
this.connValid = false
this.proto = nil
} else {
this.State = CONNECTIONSTATE_CONNECTING
this.connValid = true
this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTING})
this.proto = this.Hco.Address.GetProtocol().Create(this)
}
}
// Read from socket into our local buffer (blocking)
if this.connValid {
readBuff := make([]byte, 1024)
this.conn.SetReadDeadline(time.Now().Add(SEND_KEEPALIVE_EVERY))
nbytes, err = this.conn.Read(readBuff)
if checkIsNetTimeout(err) {
// No data before read deadline
err = nil
if this.proto == nil {
// Autodetect: switch to ADC
this.proto = NewAdcProtocol(this)
} else {
// Normal
// Send KA packet
err = this.SayKeepalive()
}
}
if nbytes > 0 {
this.lastDataRecieved = time.Now()
fullBuffer += string(readBuff[0:nbytes])
}
}
if this.proto != nil {
rxSeparator := regexp.QuoteMeta(this.proto.ProtoMessageSeparator())
rxProtocolMessage := regexp.MustCompile(`(?ms)\A[^` + rxSeparator + `]*` + rxSeparator)
// Attempt to parse a message block
for len(fullBuffer) > 0 {
// FIXME nmdc
for len(fullBuffer) > 0 && fullBuffer[0] == '|' {
fullBuffer = fullBuffer[1:]
}
protocolMessage := rxProtocolMessage.FindString(fullBuffer)
if len(protocolMessage) > 0 {
this.proto.ProcessCommand(protocolMessage[:len(protocolMessage)-1])
fullBuffer = fullBuffer[len(protocolMessage):]
} else {
break
}
}
if err == nil && time.Now().Sub(this.lastDataRecieved) > RECONNECT_IF_NO_DATA_RECIEVED_IN {
err = fmt.Errorf("No packets recieved since %s, connection presumed lost", this.lastDataRecieved.Format(time.RFC3339))
}
}
// Maybe we disconnected
// Perform this check *last*, to ensure we've had a final shot at
// clearing out any queued messages
if err != nil {
this.State = CONNECTIONSTATE_DISCONNECTED
this.conn = nil
this.connValid = false
this.proto = nil
this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_DISCONNECTED, Message: err.Error()})
if this.autoReconnect {
time.Sleep(AUTO_RECONNECT_AFTER) // Wait before reconnect
continue
} else {
return // leave the worker for good
}
}
}
}

79
vendor/code.ivysaur.me/libnmdc/HubConnectionOptions.go generated vendored Normal file
View File

@ -0,0 +1,79 @@
package libnmdc
import (
"crypto/rand"
)
type HubConnectionOptions struct {
Address HubAddress
SkipVerifyTLS bool // using a negative verb, because bools default to false
SkipAutoReconnect bool // as above
Self *UserInfo
NickPassword string
AdcPID string // blank: autogenerate
}
func NewPID() string {
pidBytes := make([]byte, 24)
n, err := rand.Read(pidBytes)
if err != nil {
panic(err) // Insufficient cryptographic randomness
}
if n != 24 {
panic("Insufficient cryptographic randomness")
}
return Base32(pidBytes)
}
func (this *HubConnectionOptions) prepareConnection() *HubConnection {
if this.Self.ClientTag == "" {
this.Self.ClientTag = DEFAULT_CLIENT_TAG
this.Self.ClientVersion = DEFAULT_CLIENT_VERSION
}
// Shouldn't be blank either
if this.Self.ClientVersion == "" {
this.Self.ClientVersion = "0"
}
if this.AdcPID == "" {
this.AdcPID = NewPID()
}
hc := HubConnection{
Hco: this,
HubName: DEFAULT_HUB_NAME,
State: CONNECTIONSTATE_DISCONNECTED,
users: make(map[string]UserInfo),
userSIDs: make(map[string]string),
autoReconnect: !this.SkipAutoReconnect,
}
return &hc
}
// ConnectAsync connects to a hub server, and spawns a background goroutine to handle
// protocol messages. Events will be sent by channel to the supplied onEvent channel,
// the client is responsible for selecting off this.
func ConnectAsync(opts *HubConnectionOptions, onEvent chan HubEvent) *HubConnection {
hc := opts.prepareConnection()
hc.processEvent = func(ev HubEvent) {
onEvent <- ev
}
go hc.worker()
return hc
}
// ConnectSync connects to a hub server, and blocks forever to handle protocol messages.
// Client code should supply an event handling function as hco.OnEventSync.
func ConnectSync(opts *HubConnectionOptions, onEvent func(hub *HubConnection, ev HubEvent)) {
hc := opts.prepareConnection()
hc.processEvent = func(ev HubEvent) {
onEvent(hc, ev)
}
hc.worker()
}

25
vendor/code.ivysaur.me/libnmdc/HubEvent.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
package libnmdc
type HubEventType int
const (
EVENT_PUBLIC HubEventType = 1
EVENT_PRIVATE HubEventType = 2
EVENT_SYSTEM_MESSAGE_FROM_HUB HubEventType = 3
EVENT_SYSTEM_MESSAGE_FROM_CONN HubEventType = 4
EVENT_USER_JOINED HubEventType = 5
EVENT_USER_PART HubEventType = 6
EVENT_USER_UPDATED_INFO HubEventType = 7
EVENT_CONNECTION_STATE_CHANGED HubEventType = 8
EVENT_HUBNAME_CHANGED HubEventType = 9
EVENT_DEBUG_MESSAGE HubEventType = 10
EVENT_USERCOMMAND HubEventType = 11
)
type HubEvent struct {
EventType HubEventType
Nick string
Message string
StateChange ConnectionState
UserCommand *UserCommand
}

407
vendor/code.ivysaur.me/libnmdc/NmdcProtocol.go generated vendored Normal file
View File

@ -0,0 +1,407 @@
package libnmdc
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
type NmdcProtocol struct {
hc *HubConnection
sentOurHello bool
supports map[string]struct{}
rxPublicChat *regexp.Regexp
rxIncomingTo *regexp.Regexp
rxUserCommand *regexp.Regexp
rxMyInfo *regexp.Regexp
rxMyInfoNoTag *regexp.Regexp
}
func NewNmdcProtocol(hc *HubConnection) Protocol {
proto := NmdcProtocol{}
proto.hc = hc
// With the `m` flag, use \A instead of ^ to anchor to start
// This fixes accidentally finding a better match in the middle of a multi-line message
proto.rxPublicChat = regexp.MustCompile(`(?ms)\A<([^>]*)> (.*)$`)
proto.rxIncomingTo = regexp.MustCompile(`(?ms)\A([^ ]+) From: ([^ ]+) \$<([^>]*)> (.*)`)
proto.rxUserCommand = regexp.MustCompile(`(?ms)\A(\d+) (\d+)\s?([^\$]*)\$?(.*)`)
// Format: $ALL <nick> <description>$ $<connection><flag>$<e-mail>$<sharesize>$
HEAD := `(?ms)^\$ALL ([^ ]+) `
FOOT := `\$.\$([^$]+)\$([^$]*)\$([0-9]*)\$$`
proto.rxMyInfo = regexp.MustCompile(HEAD + `([^<]*)<(.+?) V:([^,]+),M:(.),H:([0-9]+)/([0-9]+)/([0-9]+),S:([0-9]+)>` + FOOT)
proto.rxMyInfoNoTag = regexp.MustCompile(HEAD + `([^$]*)` + FOOT) // Fallback for no tag
// Done
return &proto
}
func (this *NmdcProtocol) ProcessCommand(message string) {
// Zero-length protocol message
// ````````````````````````````
if len(message) == 0 {
return
}
// Public chat
// ```````````
if this.rxPublicChat.MatchString(message) {
pubchat_parts := this.rxPublicChat.FindStringSubmatch(message)
this.hc.processEvent(HubEvent{EventType: EVENT_PUBLIC, Nick: pubchat_parts[1], Message: this.unescape(pubchat_parts[2])})
return
}
// System messages
// ```````````````
if message[0] != '$' {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Nick: this.hc.HubName, Message: this.unescape(message)})
return
}
// Protocol messages
// `````````````````
commandParts := strings.SplitN(message, " ", 2)
switch commandParts[0] {
case "$Lock":
this.hc.SayRaw("$Supports NoHello NoGetINFO UserCommand UserIP2 QuickList ChatOnly|" +
"$Key " + this.unlock([]byte(commandParts[1])) + "|")
this.sentOurHello = false
case "$Hello":
if commandParts[1] == this.hc.Hco.Self.Nick && !this.sentOurHello {
this.hc.SayRaw("$Version 1,0091|")
this.hc.SayRaw("$GetNickList|")
this.sayInfo()
this.sentOurHello = true
} else {
this.hc.userJoined_NameOnly(commandParts[1])
}
case "$HubName":
this.hc.HubName = commandParts[1]
this.hc.processEvent(HubEvent{EventType: EVENT_HUBNAME_CHANGED, Nick: commandParts[1]})
case "$ValidateDenide": // sic
if len(this.hc.Hco.NickPassword) > 0 {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."})
} else {
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Nick already in use."})
}
case "$HubIsFull":
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Hub is full."})
case "$BadPass":
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."})
case "$GetPass":
if len(this.hc.Hco.NickPassword) == 0 {
// We've got a problem. MyPass with no arguments is a syntax error with no message = instant close
// Just drop the connection
this.hc.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "This account is passworded."})
this.hc.Disconnect()
} else {
this.hc.SayRaw("$MyPass " + this.escape(this.hc.Hco.NickPassword) + "|")
}
case "$Quit":
this.hc.usersMut.Lock()
delete(this.hc.users, commandParts[1])
this.hc.usersMut.Unlock() // Don't lock over a processEvent boundary
this.hc.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: commandParts[1]})
case "$MyINFO":
u, err := this.parseMyINFO(commandParts[1])
if err != nil {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: err.Error()})
return
}
this.hc.userJoined_Full(u)
case "$NickList":
nicklist := strings.Split(commandParts[1], "$$")
for _, nick := range nicklist {
if len(nick) > 0 {
this.hc.userJoined_NameOnly(nick)
}
}
case "$OpList":
oplist := strings.Split(commandParts[1], "$$")
opmap := map[string]struct{}{}
// Organise/sort the list, and ensure we're not meeting an operator for
// the first time
for _, nick := range oplist {
if len(nick) > 0 {
opmap[nick] = struct{}{}
this.hc.userJoined_NameOnly(nick) // assert existence; noop otherwise
}
}
// Mark all mentioned nicks as being operators, and all unmentioned nicks
// as being /not/ an operator. (second pass minimises RW mutex use)
func() {
this.hc.usersMut.Lock()
defer this.hc.usersMut.Unlock()
for nick, userinfo := range this.hc.users {
_, isop := opmap[nick]
userinfo.IsOperator = isop
this.hc.users[nick] = userinfo
}
}()
case "$To:":
valid := false
if this.rxIncomingTo.MatchString(commandParts[1]) {
txparts := this.rxIncomingTo.FindStringSubmatch(commandParts[1])
if txparts[1] == this.hc.Hco.Self.Nick && txparts[2] == txparts[3] {
this.hc.processEvent(HubEvent{EventType: EVENT_PRIVATE, Nick: txparts[2], Message: this.unescape(txparts[4])})
valid = true
}
}
if !valid {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed private message '" + commandParts[1] + "'"})
}
case "$UserIP":
this.hc.usersMut.Lock()
pairs := strings.Split(commandParts[1], "$$")
notifyOfUpdate := make([]string, 0, len(pairs))
nextIPPair:
for _, pair := range pairs {
parts := strings.SplitN(pair, " ", 2)
if len(parts) != 2 {
// ????
continue nextIPPair
}
ip2nick := parts[0]
ip2addr := parts[1]
uinfo, ok := this.hc.users[ip2nick]
if !ok {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Recieved IP '" + ip2addr + "' for unknown user '" + ip2nick + "'"})
continue nextIPPair
}
if uinfo.IPAddress != ip2addr {
uinfo.IPAddress = ip2addr
notifyOfUpdate = append(notifyOfUpdate, ip2nick)
this.hc.users[ip2nick] = uinfo
}
}
this.hc.usersMut.Unlock()
for _, nick := range notifyOfUpdate {
this.hc.processEvent(HubEvent{EventType: EVENT_USER_UPDATED_INFO, Nick: nick})
}
case "$ForceMove":
this.hc.Hco.Address = HubAddress(commandParts[1])
this.hc.conn.Close() // we'll reconnect onto the new address
case "$UserCommand":
// $UserCommand 1 1 Group chat\New group chat$<%[mynick]> !groupchat_new&#124;|
if this.rxUserCommand.MatchString(commandParts[1]) {
usc := this.rxUserCommand.FindStringSubmatch(commandParts[1])
typeInt, _ := strconv.Atoi(usc[1])
contextInt, _ := strconv.Atoi(usc[2])
uscStruct := UserCommand{
Type: UserCommandType(typeInt),
Context: UserCommandContext(contextInt),
Message: usc[3],
Command: this.unescape(usc[4]),
}
this.hc.processEvent(HubEvent{EventType: EVENT_USERCOMMAND, UserCommand: &uscStruct})
} else {
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed usercommand '" + commandParts[1] + "'"})
}
case "$Supports":
this.supports = make(map[string]struct{})
for _, s := range strings.Split(commandParts[1], " ") {
this.supports[s] = struct{}{}
}
if !this.sentOurHello {
// Need to log in.
// If the hub supports QuickList, we can skip one network roundtrip
if _, ok := this.supports["QuickList"]; ok {
this.sayInfo()
this.hc.SayRaw("$GetNickList|")
} else {
this.hc.SayRaw("$ValidateNick " + this.escape(this.hc.Hco.Self.Nick) + "|")
}
// This also counts as the end of the handshake from our POV. Consider
// ourselves logged in
this.sentOurHello = true
if this.hc.State != CONNECTIONSTATE_CONNECTED {
this.hc.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED})
this.hc.State = CONNECTIONSTATE_CONNECTED
}
}
// IGNORABLE COMMANDS
case "$HubTopic":
case "$Search":
case "$ConnectToMe":
default:
this.hc.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Unhandled protocol command '" + commandParts[0] + "'"})
}
}
func (this *NmdcProtocol) escape(plaintext string) string {
v1 := strings.Replace(plaintext, "&", "&amp;", -1)
v2 := strings.Replace(v1, "|", "&#124;", -1)
return strings.Replace(v2, "$", "&#36;", -1)
}
func (this *NmdcProtocol) unescape(encoded string) string {
v1 := strings.Replace(encoded, "&#36;", "$", -1)
v2 := strings.Replace(v1, "&#124;", "|", -1)
return strings.Replace(v2, "&amp;", "&", -1)
}
func (this *NmdcProtocol) SayPublic(message string) {
this.hc.SayRaw("<" + this.hc.Hco.Self.Nick + "> " + this.escape(message) + "|")
}
func (this *NmdcProtocol) SayPrivate(recipient, message string) {
this.hc.SayRaw("$To: " + recipient + " From: " + this.hc.Hco.Self.Nick + " $<" + this.hc.Hco.Self.Nick + "> " + this.escape(message) + "|")
}
func (this *NmdcProtocol) sayInfo() {
this.hc.SayRaw(this.getUserMyINFO(this.hc.Hco.Self) + "|")
}
func (this *NmdcProtocol) parseMyINFO(protomsg string) (*UserInfo, error) {
ret := UserInfo{}
// Normal format (with tag in exact V/M/H/S order)
matches := this.rxMyInfo.FindStringSubmatch(protomsg)
if matches != nil {
ret.Nick = matches[1]
ret.Description = this.unescape(matches[2])
ret.ClientTag = this.unescape(matches[3])
ret.ClientVersion = matches[4]
ret.ConnectionMode = ConnectionMode(matches[5][0])
maybeParse(matches[6], &ret.HubsUnregistered, 0)
maybeParse(matches[7], &ret.HubsRegistered, 0)
maybeParse(matches[8], &ret.HubsOperator, 0)
maybeParse(matches[9], &ret.Slots, 0)
if len(matches[10]) > 1 {
ret.Speed = matches[10][:len(matches[10])-2]
} else {
ret.Speed = ""
}
ret.Flag = UserFlag(matches[10][len(matches[10])-1])
ret.Email = this.unescape(matches[11])
maybeParse(matches[12], &ret.ShareSize, 0)
return &ret, nil
}
// No-tag format, used in early connection
matches = this.rxMyInfoNoTag.FindStringSubmatch(protomsg)
if matches != nil {
ret.Nick = matches[1]
ret.Description = this.unescape(matches[2])
ret.ClientTag = ""
ret.ClientVersion = "0"
ret.ConnectionMode = CONNECTIONMODE_PASSIVE
ret.HubsUnregistered = 0
ret.HubsRegistered = 0
ret.HubsOperator = 0
ret.Slots = 0
if len(matches[3]) > 1 {
ret.Speed = matches[3][:len(matches[3])-2]
} else {
ret.Speed = ""
}
ret.Flag = UserFlag(matches[3][len(matches[3])-1])
ret.Email = this.unescape(matches[4])
maybeParse(matches[5], &ret.ShareSize, 0)
return &ret, nil
}
// Couldn't get anything out of it...
return nil, errors.New("Malformed MyINFO")
}
// Returns the MyINFO command, WITH leading $MyINFO, and WITHOUT trailing pipe
func (this *NmdcProtocol) getUserMyINFO(u *UserInfo) string {
return fmt.Sprintf(
"$MyINFO $ALL %s %s<%s V:%s,M:%c,H:%d/%d/%d,S:%d>$ $%s%c$%s$%d$",
u.Nick,
u.Description,
u.ClientTag,
strings.Replace(u.ClientVersion, ",", "-", -1), // just in case
u.ConnectionMode,
u.HubsUnregistered,
u.HubsRegistered,
u.HubsOperator,
u.Slots,
u.Speed,
u.Flag,
u.Email,
u.ShareSize,
)
}
func (this *NmdcProtocol) ProtoMessageSeparator() string {
return "|"
}
func (this *NmdcProtocol) unlock(lock []byte) string {
nibble_swap := func(b byte) byte {
return ((b << 4) & 0xF0) | ((b >> 4) & 0x0F)
}
chr := func(b byte) string {
if b == 0 || b == 5 || b == 36 || b == 96 || b == 124 || b == 126 {
return fmt.Sprintf("/%%DCN%04d%%/", b)
} else {
return string(b)
}
}
key := chr(nibble_swap(lock[0] ^ lock[len(lock)-2] ^ lock[len(lock)-3] ^ 5))
for i := 1; i < len(lock); i += 1 {
key += chr(nibble_swap(lock[i] ^ lock[i-1]))
}
return key
}

31
vendor/code.ivysaur.me/libnmdc/Protocol.go generated vendored Normal file
View File

@ -0,0 +1,31 @@
package libnmdc
type Protocol interface {
ProcessCommand(msg string)
SayPublic(string)
SayPrivate(user, message string)
ProtoMessageSeparator() string
}
type HubProtocol int
const (
HubProtocolAutodetect HubProtocol = 0
HubProtocolNmdc HubProtocol = 1
HubProtocolAdc HubProtocol = 2
)
func (hp HubProtocol) Create(hc *HubConnection) Protocol {
if hp == HubProtocolNmdc {
return NewNmdcProtocol(hc)
} else if hp == HubProtocolAdc {
return NewAdcProtocol(hc)
} else {
return NewAutodetectProtocol(hc)
}
}

6
vendor/code.ivysaur.me/libnmdc/TODO.txt generated vendored Normal file
View File

@ -0,0 +1,6 @@
NMDC:
- Implement ZPipe ($ZOn)
ADC:
- Usercommands
- ???

27
vendor/code.ivysaur.me/libnmdc/UserCommand.go generated vendored Normal file
View File

@ -0,0 +1,27 @@
package libnmdc
type UserCommandType uint8
const (
USERCOMMAND_TYPE_SEPARATOR UserCommandType = 0
USERCOMMAND_TYPE_RAW UserCommandType = 1
USERCOMMAND_TYPE_NICKLIMITED UserCommandType = 2
USERCOMMAND_TYPE_CLEARALL UserCommandType = 255
)
type UserCommandContext uint8
const (
USERCOMMAND_CONTEXT_HUB UserCommandContext = 1
USERCOMMAND_CONTEXT_USER UserCommandContext = 2
USERCOMMAND_CONTEXT_SEARCH UserCommandContext = 4
USERCOMMAND_CONTEXT_FILELIST UserCommandContext = 8
)
type UserCommand struct {
Type UserCommandType
Context UserCommandContext
Message string
Command string
RemoveThis bool // Currently only set by ADC hubs
}

17
vendor/code.ivysaur.me/libnmdc/UserFlag.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
package libnmdc
type UserFlag byte
const (
FLAG_NORMAL UserFlag = 1
FLAG_AWAY_1 UserFlag = 2
FLAG_AWAY_2 UserFlag = 3
FLAG_SERVER_1 UserFlag = 4
FLAG_SERVER_2 UserFlag = 5
FLAG_SERVER_AWAY_1 UserFlag = 6
FLAG_SERVER_AWAY_2 UserFlag = 7
FLAG_FIREBALL_1 UserFlag = 8
FLAG_FIREBALL_2 UserFlag = 9
FLAG_FIREBALL_AWAY_1 UserFlag = 10
FLAG_FIREBALL_AWAY_2 UserFlag = 11
)

53
vendor/code.ivysaur.me/libnmdc/UserInfo.go generated vendored Normal file
View File

@ -0,0 +1,53 @@
package libnmdc
// This structure represents a user connected to a hub.
type UserInfo struct {
Nick string
Description string
ClientTag string
ClientVersion string
Email string
ShareSize uint64
Flag UserFlag
Slots uint64
HubsUnregistered uint64
HubsRegistered uint64
HubsOperator uint64
IsOperator bool
UserInfo_NMDCOnly
UserInfo_ADCOnly
}
type UserInfo_NMDCOnly struct {
Speed string
IPAddress string
ConnectionMode ConnectionMode
}
type UserInfo_ADCOnly struct {
SharedFiles uint64
UploadSpeedBps uint64
DownloadSpeedBps uint64
IsBot bool
IsRegistered bool
IsSuperUser bool
IsHubOwner bool
IPv4Address string // Passive <==> these fields are not set
IPv6Address string
IPv4UDPPort uint64
IPv6UDPPort uint64
Keyprint string
CID string
SupportFlags map[string]struct{}
}
func NewUserInfo(username string) *UserInfo {
return &UserInfo{
Nick: username,
HubsUnregistered: 1,
UserInfo_NMDCOnly: UserInfo_NMDCOnly{
ConnectionMode: CONNECTIONMODE_PASSIVE,
},
}
}

28
vendor/code.ivysaur.me/libnmdc/libnmdc.go generated vendored Normal file
View File

@ -0,0 +1,28 @@
package libnmdc
import (
"errors"
"strconv"
"time"
)
const (
DEFAULT_CLIENT_TAG string = "libnmdc.go"
DEFAULT_CLIENT_VERSION string = "0.16"
DEFAULT_HUB_NAME string = "(unknown)"
SEND_KEEPALIVE_EVERY time.Duration = 29 * time.Second
AUTO_RECONNECT_AFTER time.Duration = 30 * time.Second
RECONNECT_IF_NO_DATA_RECIEVED_IN time.Duration = 24 * time.Hour // we expect keepalives wayyyy more frequently than this
AUTODETECT_ADC_NMDC_TIMEOUT time.Duration = 3 * time.Second
)
var ErrNotConnected error = errors.New("Not connected")
func maybeParse(str string, dest *uint64, default_val uint64) {
sz, err := strconv.ParseUint(str, 10, 64)
if err == nil {
*dest = sz
} else {
*dest = default_val
}
}

41
vendor/code.ivysaur.me/libnmdc/tth.go generated vendored Normal file
View File

@ -0,0 +1,41 @@
package libnmdc
import (
"encoding/base32"
"errors"
"strings"
"github.com/cxmcc/tiger"
)
// Base32 encodes the input slice in BASE32 string format without any trailing
// padding equals characters.
func Base32(input []byte) string {
return strings.TrimRight(base32.StdEncoding.EncodeToString(input), "=")
}
// TTH returns the TTH hash of a string in raw byte format. Use the Base32()
// function to convert it to normal string format.
// This is a basic implementation that only supports content up to 1024 bytes in length.
func TTH(input string) ([]byte, error) {
// Short segments do not need to be padded.
// Content above 1024 bytes needs tree handling (0x00 prefix for leaf nodes,
// 0x01 prefix for hash nodes) but for content less than 1024 bytes, just
// return the leaf hash
// @ref http://adc.sourceforge.net/draft-jchapweske-thex-02.html
if len(input) > 1024 {
return nil, errors.New("TTH content exceeded 1024 bytes")
}
// Single leaf hash only
leafHash := tiger.New()
leafHash.Write([]byte("\x00" + input))
return leafHash.Sum(nil), nil
}
func Tiger(input string) []byte {
leafHash := tiger.New()
leafHash.Write([]byte(input))
return leafHash.Sum(nil)
}

22
vendor/github.com/cxmcc/tiger/.gitignore generated vendored Normal file
View File

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

7
vendor/github.com/cxmcc/tiger/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,7 @@
language: go
go:
- 1.x
- 1.6
- 1.7.x
- master

20
vendor/github.com/cxmcc/tiger/LICENSE generated vendored Normal file
View File

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

48
vendor/github.com/cxmcc/tiger/README.md generated vendored Normal file
View File

@ -0,0 +1,48 @@
Tiger cryptographic hash function for Go
-----
[![Build Status](https://travis-ci.org/cxmcc/tiger.svg?branch=master)](https://travis-ci.org/cxmcc/tiger)
[![GoDoc](http://godoc.org/github.com/cxmcc/tiger?status.png)](http://godoc.org/github.com/cxmcc/tiger)
### About Tiger
* Tiger cryptographic hash function is designed by Ross Anderson and Eli Biham in 1995.
* The size of a Tiger hash value is 192 bits. Truncated versions (Tiger/128, Tiger/160) are simply prefixes of Tiger/192.
* Tiger2 is a variant where the message is padded by first appending a byte 0x80, rather than 0x01 as in the case of Tiger.
* Links: [paper](http://www.cs.technion.ac.il/~biham/Reports/Tiger/), [wikipedia](http://en.wikipedia.org/wiki/Tiger_\(cryptography\))
### API Documentation
Implementing [hash.Hash](http://golang.org/pkg/hash/#Hash). Usage is pretty much the same as other stanard hashing libraries.
Documentation currently available at Godoc: [http://godoc.org/github.com/cxmcc/tiger](http://godoc.org/github.com/cxmcc/tiger)
### Installing
~~~
go get github.com/cxmcc/tiger
~~~
### Example
~~~ go
package main
import (
"fmt"
"io"
"github.com/cxmcc/tiger"
)
func main() {
h := tiger.New()
io.WriteString(h, "Example for tiger")
fmt.Printf("Output: %x\n", h.Sum(nil))
// Output: 82bd060e19f945014f0063e8f0e6d7decfa9edfd97e76743
}
~~~
### License
It's MIT License

96
vendor/github.com/cxmcc/tiger/compress.go generated vendored Normal file
View File

@ -0,0 +1,96 @@
package tiger
import (
"encoding/binary"
"unsafe"
)
var littleEndian bool
func init() {
x := uint32(0x04030201)
y := [4]byte{0x1, 0x2, 0x3, 0x4}
littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y
}
func pass(a, b, c uint64, x []uint64, mul uint64) (uint64, uint64, uint64) {
a, b, c = round(a, b, c, x[0], mul)
b, c, a = round(b, c, a, x[1], mul)
c, a, b = round(c, a, b, x[2], mul)
a, b, c = round(a, b, c, x[3], mul)
b, c, a = round(b, c, a, x[4], mul)
c, a, b = round(c, a, b, x[5], mul)
a, b, c = round(a, b, c, x[6], mul)
b, c, a = round(b, c, a, x[7], mul)
return a, b, c
}
func round(a, b, c, x, mul uint64) (uint64, uint64, uint64) {
c ^= x
a -= t1[c&0xff] ^ t2[(c>>16)&0xff] ^ t3[(c>>32)&0xff] ^ t4[(c>>48)&0xff]
b += t4[(c>>8)&0xff] ^ t3[(c>>24)&0xff] ^ t2[(c>>40)&0xff] ^ t1[(c>>56)&0xff]
b *= mul
return a, b, c
}
func keySchedule(x []uint64) {
x[0] -= x[7] ^ 0xa5a5a5a5a5a5a5a5
x[1] ^= x[0]
x[2] += x[1]
x[3] -= x[2] ^ ((^x[1]) << 19)
x[4] ^= x[3]
x[5] += x[4]
x[6] -= x[5] ^ ((^x[4]) >> 23)
x[7] ^= x[6]
x[0] += x[7]
x[1] -= x[0] ^ ((^x[7]) << 19)
x[2] ^= x[1]
x[3] += x[2]
x[4] -= x[3] ^ ((^x[2]) >> 23)
x[5] ^= x[4]
x[6] += x[5]
x[7] -= x[6] ^ 0x0123456789abcdef
}
func (d *digest) compress(data []byte) {
// save_abc
a := d.a
b := d.b
c := d.c
var x []uint64
if littleEndian {
x = []uint64{
binary.LittleEndian.Uint64(data[0:8]),
binary.LittleEndian.Uint64(data[8:16]),
binary.LittleEndian.Uint64(data[16:24]),
binary.LittleEndian.Uint64(data[24:32]),
binary.LittleEndian.Uint64(data[32:40]),
binary.LittleEndian.Uint64(data[40:48]),
binary.LittleEndian.Uint64(data[48:56]),
binary.LittleEndian.Uint64(data[56:64]),
}
} else {
x = []uint64{
binary.BigEndian.Uint64(data[0:8]),
binary.BigEndian.Uint64(data[8:16]),
binary.BigEndian.Uint64(data[16:24]),
binary.BigEndian.Uint64(data[24:32]),
binary.BigEndian.Uint64(data[32:40]),
binary.BigEndian.Uint64(data[40:48]),
binary.BigEndian.Uint64(data[48:56]),
binary.BigEndian.Uint64(data[56:64]),
}
}
d.a, d.b, d.c = pass(d.a, d.b, d.c, x, 5)
keySchedule(x)
d.c, d.a, d.b = pass(d.c, d.a, d.b, x, 7)
keySchedule(x)
d.b, d.c, d.a = pass(d.b, d.c, d.a, x, 9)
// feedforward
d.a ^= a
d.b -= b
d.c += c
}

269
vendor/github.com/cxmcc/tiger/sboxes.go generated vendored Normal file
View File

@ -0,0 +1,269 @@
package tiger
var t1 = [...]uint64{
0x02aab17cf7e90c5e, 0xac424b03e243a8ec, 0x72cd5be30dd5fcd3, 0x6d019b93f6f97f3a,
0xcd9978ffd21f9193, 0x7573a1c9708029e2, 0xb164326b922a83c3, 0x46883eee04915870,
0xeaace3057103ece6, 0xc54169b808a3535c, 0x4ce754918ddec47c, 0x0aa2f4dfdc0df40c,
0x10b76f18a74dbefa, 0xc6ccb6235ad1ab6a, 0x13726121572fe2ff, 0x1a488c6f199d921e,
0x4bc9f9f4da0007ca, 0x26f5e6f6e85241c7, 0x859079dbea5947b6, 0x4f1885c5c99e8c92,
0xd78e761ea96f864b, 0x8e36428c52b5c17d, 0x69cf6827373063c1, 0xb607c93d9bb4c56e,
0x7d820e760e76b5ea, 0x645c9cc6f07fdc42, 0xbf38a078243342e0, 0x5f6b343c9d2e7d04,
0xf2c28aeb600b0ec6, 0x6c0ed85f7254bcac, 0x71592281a4db4fe5, 0x1967fa69ce0fed9f,
0xfd5293f8b96545db, 0xc879e9d7f2a7600b, 0x860248920193194e, 0xa4f9533b2d9cc0b3,
0x9053836c15957613, 0xdb6dcf8afc357bf1, 0x18beea7a7a370f57, 0x037117ca50b99066,
0x6ab30a9774424a35, 0xf4e92f02e325249b, 0x7739db07061ccae1, 0xd8f3b49ceca42a05,
0xbd56be3f51382f73, 0x45faed5843b0bb28, 0x1c813d5c11bf1f83, 0x8af0e4b6d75fa169,
0x33ee18a487ad9999, 0x3c26e8eab1c94410, 0xb510102bc0a822f9, 0x141eef310ce6123b,
0xfc65b90059ddb154, 0xe0158640c5e0e607, 0x884e079826c3a3cf, 0x930d0d9523c535fd,
0x35638d754e9a2b00, 0x4085fccf40469dd5, 0xc4b17ad28be23a4c, 0xcab2f0fc6a3e6a2e,
0x2860971a6b943fcd, 0x3dde6ee212e30446, 0x6222f32ae01765ae, 0x5d550bb5478308fe,
0xa9efa98da0eda22a, 0xc351a71686c40da7, 0x1105586d9c867c84, 0xdcffee85fda22853,
0xccfbd0262c5eef76, 0xbaf294cb8990d201, 0xe69464f52afad975, 0x94b013afdf133e14,
0x06a7d1a32823c958, 0x6f95fe5130f61119, 0xd92ab34e462c06c0, 0xed7bde33887c71d2,
0x79746d6e6518393e, 0x5ba419385d713329, 0x7c1ba6b948a97564, 0x31987c197bfdac67,
0xde6c23c44b053d02, 0x581c49fed002d64d, 0xdd474d6338261571, 0xaa4546c3e473d062,
0x928fce349455f860, 0x48161bbacaab94d9, 0x63912430770e6f68, 0x6ec8a5e602c6641c,
0x87282515337ddd2b, 0x2cda6b42034b701b, 0xb03d37c181cb096d, 0xe108438266c71c6f,
0x2b3180c7eb51b255, 0xdf92b82f96c08bbc, 0x5c68c8c0a632f3ba, 0x5504cc861c3d0556,
0xabbfa4e55fb26b8f, 0x41848b0ab3baceb4, 0xb334a273aa445d32, 0xbca696f0a85ad881,
0x24f6ec65b528d56c, 0x0ce1512e90f4524a, 0x4e9dd79d5506d35a, 0x258905fac6ce9779,
0x2019295b3e109b33, 0xf8a9478b73a054cc, 0x2924f2f934417eb0, 0x3993357d536d1bc4,
0x38a81ac21db6ff8b, 0x47c4fbf17d6016bf, 0x1e0faadd7667e3f5, 0x7abcff62938beb96,
0xa78dad948fc179c9, 0x8f1f98b72911e50d, 0x61e48eae27121a91, 0x4d62f7ad31859808,
0xeceba345ef5ceaeb, 0xf5ceb25ebc9684ce, 0xf633e20cb7f76221, 0xa32cdf06ab8293e4,
0x985a202ca5ee2ca4, 0xcf0b8447cc8a8fb1, 0x9f765244979859a3, 0xa8d516b1a1240017,
0x0bd7ba3ebb5dc726, 0xe54bca55b86adb39, 0x1d7a3afd6c478063, 0x519ec608e7669edd,
0x0e5715a2d149aa23, 0x177d4571848ff194, 0xeeb55f3241014c22, 0x0f5e5ca13a6e2ec2,
0x8029927b75f5c361, 0xad139fabc3d6e436, 0x0d5df1a94ccf402f, 0x3e8bd948bea5dfc8,
0xa5a0d357bd3ff77e, 0xa2d12e251f74f645, 0x66fd9e525e81a082, 0x2e0c90ce7f687a49,
0xc2e8bcbeba973bc5, 0x000001bce509745f, 0x423777bbe6dab3d6, 0xd1661c7eaef06eb5,
0xa1781f354daacfd8, 0x2d11284a2b16affc, 0xf1fc4f67fa891d1f, 0x73ecc25dcb920ada,
0xae610c22c2a12651, 0x96e0a810d356b78a, 0x5a9a381f2fe7870f, 0xd5ad62ede94e5530,
0xd225e5e8368d1427, 0x65977b70c7af4631, 0x99f889b2de39d74f, 0x233f30bf54e1d143,
0x9a9675d3d9a63c97, 0x5470554ff334f9a8, 0x166acb744a4f5688, 0x70c74caab2e4aead,
0xf0d091646f294d12, 0x57b82a89684031d1, 0xefd95a5a61be0b6b, 0x2fbd12e969f2f29a,
0x9bd37013feff9fe8, 0x3f9b0404d6085a06, 0x4940c1f3166cfe15, 0x09542c4dcdf3defb,
0xb4c5218385cd5ce3, 0xc935b7dc4462a641, 0x3417f8a68ed3b63f, 0xb80959295b215b40,
0xf99cdaef3b8c8572, 0x018c0614f8fcb95d, 0x1b14accd1a3acdf3, 0x84d471f200bb732d,
0xc1a3110e95e8da16, 0x430a7220bf1a82b8, 0xb77e090d39df210e, 0x5ef4bd9f3cd05e9d,
0x9d4ff6da7e57a444, 0xda1d60e183d4a5f8, 0xb287c38417998e47, 0xfe3edc121bb31886,
0xc7fe3ccc980ccbef, 0xe46fb590189bfd03, 0x3732fd469a4c57dc, 0x7ef700a07cf1ad65,
0x59c64468a31d8859, 0x762fb0b4d45b61f6, 0x155baed099047718, 0x68755e4c3d50baa6,
0xe9214e7f22d8b4df, 0x2addbf532eac95f4, 0x32ae3909b4bd0109, 0x834df537b08e3450,
0xfa209da84220728d, 0x9e691d9b9efe23f7, 0x0446d288c4ae8d7f, 0x7b4cc524e169785b,
0x21d87f0135ca1385, 0xcebb400f137b8aa5, 0x272e2b66580796be, 0x3612264125c2b0de,
0x057702bdad1efbb2, 0xd4babb8eacf84be9, 0x91583139641bc67b, 0x8bdc2de08036e024,
0x603c8156f49f68ed, 0xf7d236f7dbef5111, 0x9727c4598ad21e80, 0xa08a0896670a5fd7,
0xcb4a8f4309eba9cb, 0x81af564b0f7036a1, 0xc0b99aa778199abd, 0x959f1ec83fc8e952,
0x8c505077794a81b9, 0x3acaaf8f056338f0, 0x07b43f50627a6778, 0x4a44ab49f5eccc77,
0x3bc3d6e4b679ee98, 0x9cc0d4d1cf14108c, 0x4406c00b206bc8a0, 0x82a18854c8d72d89,
0x67e366b35c3c432c, 0xb923dd61102b37f2, 0x56ab2779d884271d, 0xbe83e1b0ff1525af,
0xfb7c65d4217e49a9, 0x6bdbe0e76d48e7d4, 0x08df828745d9179e, 0x22ea6a9add53bd34,
0xe36e141c5622200a, 0x7f805d1b8cb750ee, 0xafe5c7a59f58e837, 0xe27f996a4fb1c23c,
0xd3867dfb0775f0d0, 0xd0e673de6e88891a, 0x123aeb9eafb86c25, 0x30f1d5d5c145b895,
0xbb434a2dee7269e7, 0x78cb67ecf931fa38, 0xf33b0372323bbf9c, 0x52d66336fb279c74,
0x505f33ac0afb4eaa, 0xe8a5cd99a2cce187, 0x534974801e2d30bb, 0x8d2d5711d5876d90,
0x1f1a412891bc038e, 0xd6e2e71d82e56648, 0x74036c3a497732b7, 0x89b67ed96361f5ab,
0xffed95d8f1ea02a2, 0xe72b3bd61464d43d, 0xa6300f170bdc4820, 0xebc18760ed78a77a,
}
var t2 = [...]uint64{
0xe6a6be5a05a12138, 0xb5a122a5b4f87c98, 0x563c6089140b6990, 0x4c46cb2e391f5dd5,
0xd932addbc9b79434, 0x08ea70e42015aff5, 0xd765a6673e478cf1, 0xc4fb757eab278d99,
0xdf11c6862d6e0692, 0xddeb84f10d7f3b16, 0x6f2ef604a665ea04, 0x4a8e0f0ff0e0dfb3,
0xa5edeef83dbcba51, 0xfc4f0a2a0ea4371e, 0xe83e1da85cb38429, 0xdc8ff882ba1b1ce2,
0xcd45505e8353e80d, 0x18d19a00d4db0717, 0x34a0cfeda5f38101, 0x0be77e518887caf2,
0x1e341438b3c45136, 0xe05797f49089ccf9, 0xffd23f9df2591d14, 0x543dda228595c5cd,
0x661f81fd99052a33, 0x8736e641db0f7b76, 0x15227725418e5307, 0xe25f7f46162eb2fa,
0x48a8b2126c13d9fe, 0xafdc541792e76eea, 0x03d912bfc6d1898f, 0x31b1aafa1b83f51b,
0xf1ac2796e42ab7d9, 0x40a3a7d7fcd2ebac, 0x1056136d0afbbcc5, 0x7889e1dd9a6d0c85,
0xd33525782a7974aa, 0xa7e25d09078ac09b, 0xbd4138b3eac6edd0, 0x920abfbe71eb9e70,
0xa2a5d0f54fc2625c, 0xc054e36b0b1290a3, 0xf6dd59ff62fe932b, 0x3537354511a8ac7d,
0xca845e9172fadcd4, 0x84f82b60329d20dc, 0x79c62ce1cd672f18, 0x8b09a2add124642c,
0xd0c1e96a19d9e726, 0x5a786a9b4ba9500c, 0x0e020336634c43f3, 0xc17b474aeb66d822,
0x6a731ae3ec9baac2, 0x8226667ae0840258, 0x67d4567691caeca5, 0x1d94155c4875adb5,
0x6d00fd985b813fdf, 0x51286efcb774cd06, 0x5e8834471fa744af, 0xf72ca0aee761ae2e,
0xbe40e4cdaee8e09a, 0xe9970bbb5118f665, 0x726e4beb33df1964, 0x703b000729199762,
0x4631d816f5ef30a7, 0xb880b5b51504a6be, 0x641793c37ed84b6c, 0x7b21ed77f6e97d96,
0x776306312ef96b73, 0xae528948e86ff3f4, 0x53dbd7f286a3f8f8, 0x16cadce74cfc1063,
0x005c19bdfa52c6dd, 0x68868f5d64d46ad3, 0x3a9d512ccf1e186a, 0x367e62c2385660ae,
0xe359e7ea77dcb1d7, 0x526c0773749abe6e, 0x735ae5f9d09f734b, 0x493fc7cc8a558ba8,
0xb0b9c1533041ab45, 0x321958ba470a59bd, 0x852db00b5f46c393, 0x91209b2bd336b0e5,
0x6e604f7d659ef19f, 0xb99a8ae2782ccb24, 0xccf52ab6c814c4c7, 0x4727d9afbe11727b,
0x7e950d0c0121b34d, 0x756f435670ad471f, 0xf5add442615a6849, 0x4e87e09980b9957a,
0x2acfa1df50aee355, 0xd898263afd2fd556, 0xc8f4924dd80c8fd6, 0xcf99ca3d754a173a,
0xfe477bacaf91bf3c, 0xed5371f6d690c12d, 0x831a5c285e687094, 0xc5d3c90a3708a0a4,
0x0f7f903717d06580, 0x19f9bb13b8fdf27f, 0xb1bd6f1b4d502843, 0x1c761ba38fff4012,
0x0d1530c4e2e21f3b, 0x8943ce69a7372c8a, 0xe5184e11feb5ce66, 0x618bdb80bd736621,
0x7d29bad68b574d0b, 0x81bb613e25e6fe5b, 0x071c9c10bc07913f, 0xc7beeb7909ac2d97,
0xc3e58d353bc5d757, 0xeb017892f38f61e8, 0xd4effb9c9b1cc21a, 0x99727d26f494f7ab,
0xa3e063a2956b3e03, 0x9d4a8b9a4aa09c30, 0x3f6ab7d500090fb4, 0x9cc0f2a057268ac0,
0x3dee9d2dedbf42d1, 0x330f49c87960a972, 0xc6b2720287421b41, 0x0ac59ec07c00369c,
0xef4eac49cb353425, 0xf450244eef0129d8, 0x8acc46e5caf4deb6, 0x2ffeab63989263f7,
0x8f7cb9fe5d7a4578, 0x5bd8f7644e634635, 0x427a7315bf2dc900, 0x17d0c4aa2125261c,
0x3992486c93518e50, 0xb4cbfee0a2d7d4c3, 0x7c75d6202c5ddd8d, 0xdbc295d8e35b6c61,
0x60b369d302032b19, 0xce42685fdce44132, 0x06f3ddb9ddf65610, 0x8ea4d21db5e148f0,
0x20b0fce62fcd496f, 0x2c1b912358b0ee31, 0xb28317b818f5a308, 0xa89c1e189ca6d2cf,
0x0c6b18576aaadbc8, 0xb65deaa91299fae3, 0xfb2b794b7f1027e7, 0x04e4317f443b5beb,
0x4b852d325939d0a6, 0xd5ae6beefb207ffc, 0x309682b281c7d374, 0xbae309a194c3b475,
0x8cc3f97b13b49f05, 0x98a9422ff8293967, 0x244b16b01076ff7c, 0xf8bf571c663d67ee,
0x1f0d6758eee30da1, 0xc9b611d97adeb9b7, 0xb7afd5887b6c57a2, 0x6290ae846b984fe1,
0x94df4cdeacc1a5fd, 0x058a5bd1c5483aff, 0x63166cc142ba3c37, 0x8db8526eb2f76f40,
0xe10880036f0d6d4e, 0x9e0523c9971d311d, 0x45ec2824cc7cd691, 0x575b8359e62382c9,
0xfa9e400dc4889995, 0xd1823ecb45721568, 0xdafd983b8206082f, 0xaa7d29082386a8cb,
0x269fcd4403b87588, 0x1b91f5f728bdd1e0, 0xe4669f39040201f6, 0x7a1d7c218cf04ade,
0x65623c29d79ce5ce, 0x2368449096c00bb1, 0xab9bf1879da503ba, 0xbc23ecb1a458058e,
0x9a58df01bb401ecc, 0xa070e868a85f143d, 0x4ff188307df2239e, 0x14d565b41a641183,
0xee13337452701602, 0x950e3dcf3f285e09, 0x59930254b9c80953, 0x3bf299408930da6d,
0xa955943f53691387, 0xa15edecaa9cb8784, 0x29142127352be9a0, 0x76f0371fff4e7afb,
0x0239f450274f2228, 0xbb073af01d5e868b, 0xbfc80571c10e96c1, 0xd267088568222e23,
0x9671a3d48e80b5b0, 0x55b5d38ae193bb81, 0x693ae2d0a18b04b8, 0x5c48b4ecadd5335f,
0xfd743b194916a1ca, 0x2577018134be98c4, 0xe77987e83c54a4ad, 0x28e11014da33e1b9,
0x270cc59e226aa213, 0x71495f756d1a5f60, 0x9be853fb60afef77, 0xadc786a7f7443dbf,
0x0904456173b29a82, 0x58bc7a66c232bd5e, 0xf306558c673ac8b2, 0x41f639c6b6c9772a,
0x216defe99fda35da, 0x11640cc71c7be615, 0x93c43694565c5527, 0xea038e6246777839,
0xf9abf3ce5a3e2469, 0x741e768d0fd312d2, 0x0144b883ced652c6, 0xc20b5a5ba33f8552,
0x1ae69633c3435a9d, 0x97a28ca4088cfdec, 0x8824a43c1e96f420, 0x37612fa66eeea746,
0x6b4cb165f9cf0e5a, 0x43aa1c06a0abfb4a, 0x7f4dc26ff162796b, 0x6cbacc8e54ed9b0f,
0xa6b7ffefd2bb253e, 0x2e25bc95b0a29d4f, 0x86d6a58bdef1388c, 0xded74ac576b6f054,
0x8030bdbc2b45805d, 0x3c81af70e94d9289, 0x3eff6dda9e3100db, 0xb38dc39fdfcc8847,
0x123885528d17b87e, 0xf2da0ed240b1b642, 0x44cefadcd54bf9a9, 0x1312200e433c7ee6,
0x9ffcc84f3a78c748, 0xf0cd1f72248576bb, 0xec6974053638cfe4, 0x2ba7b67c0cec4e4c,
0xac2f4df3e5ce32ed, 0xcb33d14326ea4c11, 0xa4e9044cc77e58bc, 0x5f513293d934fcef,
0x5dc9645506e55444, 0x50de418f317de40a, 0x388cb31a69dde259, 0x2db4a83455820a86,
0x9010a91e84711ae9, 0x4df7f0b7b1498371, 0xd62a2eabc0977179, 0x22fac097aa8d5c0e,
}
var t3 = [...]uint64{
0xf49fcc2ff1daf39b, 0x487fd5c66ff29281, 0xe8a30667fcdca83f, 0x2c9b4be3d2fcce63,
0xda3ff74b93fbbbc2, 0x2fa165d2fe70ba66, 0xa103e279970e93d4, 0xbecdec77b0e45e71,
0xcfb41e723985e497, 0xb70aaa025ef75017, 0xd42309f03840b8e0, 0x8efc1ad035898579,
0x96c6920be2b2abc5, 0x66af4163375a9172, 0x2174abdcca7127fb, 0xb33ccea64a72ff41,
0xf04a4933083066a5, 0x8d970acdd7289af5, 0x8f96e8e031c8c25e, 0xf3fec02276875d47,
0xec7bf310056190dd, 0xf5adb0aebb0f1491, 0x9b50f8850fd58892, 0x4975488358b74de8,
0xa3354ff691531c61, 0x0702bbe481d2c6ee, 0x89fb24057deded98, 0xac3075138596e902,
0x1d2d3580172772ed, 0xeb738fc28e6bc30d, 0x5854ef8f63044326, 0x9e5c52325add3bbe,
0x90aa53cf325c4623, 0xc1d24d51349dd067, 0x2051cfeea69ea624, 0x13220f0a862e7e4f,
0xce39399404e04864, 0xd9c42ca47086fcb7, 0x685ad2238a03e7cc, 0x066484b2ab2ff1db,
0xfe9d5d70efbf79ec, 0x5b13b9dd9c481854, 0x15f0d475ed1509ad, 0x0bebcd060ec79851,
0xd58c6791183ab7f8, 0xd1187c5052f3eee4, 0xc95d1192e54e82ff, 0x86eea14cb9ac6ca2,
0x3485beb153677d5d, 0xdd191d781f8c492a, 0xf60866baa784ebf9, 0x518f643ba2d08c74,
0x8852e956e1087c22, 0xa768cb8dc410ae8d, 0x38047726bfec8e1a, 0xa67738b4cd3b45aa,
0xad16691cec0dde19, 0xc6d4319380462e07, 0xc5a5876d0ba61938, 0x16b9fa1fa58fd840,
0x188ab1173ca74f18, 0xabda2f98c99c021f, 0x3e0580ab134ae816, 0x5f3b05b773645abb,
0x2501a2be5575f2f6, 0x1b2f74004e7e8ba9, 0x1cd7580371e8d953, 0x7f6ed89562764e30,
0xb15926ff596f003d, 0x9f65293da8c5d6b9, 0x6ecef04dd690f84c, 0x4782275fff33af88,
0xe41433083f820801, 0xfd0dfe409a1af9b5, 0x4325a3342cdb396b, 0x8ae77e62b301b252,
0xc36f9e9f6655615a, 0x85455a2d92d32c09, 0xf2c7dea949477485, 0x63cfb4c133a39eba,
0x83b040cc6ebc5462, 0x3b9454c8fdb326b0, 0x56f56a9e87ffd78c, 0x2dc2940d99f42bc6,
0x98f7df096b096e2d, 0x19a6e01e3ad852bf, 0x42a99ccbdbd4b40b, 0xa59998af45e9c559,
0x366295e807d93186, 0x6b48181bfaa1f773, 0x1fec57e2157a0a1d, 0x4667446af6201ad5,
0xe615ebcacfb0f075, 0xb8f31f4f68290778, 0x22713ed6ce22d11e, 0x3057c1a72ec3c93b,
0xcb46acc37c3f1f2f, 0xdbb893fd02aaf50e, 0x331fd92e600b9fcf, 0xa498f96148ea3ad6,
0xa8d8426e8b6a83ea, 0xa089b274b7735cdc, 0x87f6b3731e524a11, 0x118808e5cbc96749,
0x9906e4c7b19bd394, 0xafed7f7e9b24a20c, 0x6509eadeeb3644a7, 0x6c1ef1d3e8ef0ede,
0xb9c97d43e9798fb4, 0xa2f2d784740c28a3, 0x7b8496476197566f, 0x7a5be3e6b65f069d,
0xf96330ed78be6f10, 0xeee60de77a076a15, 0x2b4bee4aa08b9bd0, 0x6a56a63ec7b8894e,
0x02121359ba34fef4, 0x4cbf99f8283703fc, 0x398071350caf30c8, 0xd0a77a89f017687a,
0xf1c1a9eb9e423569, 0x8c7976282dee8199, 0x5d1737a5dd1f7abd, 0x4f53433c09a9fa80,
0xfa8b0c53df7ca1d9, 0x3fd9dcbc886ccb77, 0xc040917ca91b4720, 0x7dd00142f9d1dcdf,
0x8476fc1d4f387b58, 0x23f8e7c5f3316503, 0x032a2244e7e37339, 0x5c87a5d750f5a74b,
0x082b4cc43698992e, 0xdf917becb858f63c, 0x3270b8fc5bf86dda, 0x10ae72bb29b5dd76,
0x576ac94e7700362b, 0x1ad112dac61efb8f, 0x691bc30ec5faa427, 0xff246311cc327143,
0x3142368e30e53206, 0x71380e31e02ca396, 0x958d5c960aad76f1, 0xf8d6f430c16da536,
0xc8ffd13f1be7e1d2, 0x7578ae66004ddbe1, 0x05833f01067be646, 0xbb34b5ad3bfe586d,
0x095f34c9a12b97f0, 0x247ab64525d60ca8, 0xdcdbc6f3017477d1, 0x4a2e14d4decad24d,
0xbdb5e6d9be0a1eeb, 0x2a7e70f7794301ab, 0xdef42d8a270540fd, 0x01078ec0a34c22c1,
0xe5de511af4c16387, 0x7ebb3a52bd9a330a, 0x77697857aa7d6435, 0x004e831603ae4c32,
0xe7a21020ad78e312, 0x9d41a70c6ab420f2, 0x28e06c18ea1141e6, 0xd2b28cbd984f6b28,
0x26b75f6c446e9d83, 0xba47568c4d418d7f, 0xd80badbfe6183d8e, 0x0e206d7f5f166044,
0xe258a43911cbca3e, 0x723a1746b21dc0bc, 0xc7caa854f5d7cdd3, 0x7cac32883d261d9c,
0x7690c26423ba942c, 0x17e55524478042b8, 0xe0be477656a2389f, 0x4d289b5e67ab2da0,
0x44862b9c8fbbfd31, 0xb47cc8049d141365, 0x822c1b362b91c793, 0x4eb14655fb13dfd8,
0x1ecbba0714e2a97b, 0x6143459d5cde5f14, 0x53a8fbf1d5f0ac89, 0x97ea04d81c5e5b00,
0x622181a8d4fdb3f3, 0xe9bcd341572a1208, 0x1411258643cce58a, 0x9144c5fea4c6e0a4,
0x0d33d06565cf620f, 0x54a48d489f219ca1, 0xc43e5eac6d63c821, 0xa9728b3a72770daf,
0xd7934e7b20df87ef, 0xe35503b61a3e86e5, 0xcae321fbc819d504, 0x129a50b3ac60bfa6,
0xcd5e68ea7e9fb6c3, 0xb01c90199483b1c7, 0x3de93cd5c295376c, 0xaed52edf2ab9ad13,
0x2e60f512c0a07884, 0xbc3d86a3e36210c9, 0x35269d9b163951ce, 0x0c7d6e2ad0cdb5fa,
0x59e86297d87f5733, 0x298ef221898db0e7, 0x55000029d1a5aa7e, 0x8bc08ae1b5061b45,
0xc2c31c2b6c92703a, 0x94cc596baf25ef42, 0x0a1d73db22540456, 0x04b6a0f9d9c4179a,
0xeffdafa2ae3d3c60, 0xf7c8075bb49496c4, 0x9cc5c7141d1cd4e3, 0x78bd1638218e5534,
0xb2f11568f850246a, 0xedfabcfa9502bc29, 0x796ce5f2da23051b, 0xaae128b0dc93537c,
0x3a493da0ee4b29ae, 0xb5df6b2c416895d7, 0xfcabbd25122d7f37, 0x70810b58105dc4b1,
0xe10fdd37f7882a90, 0x524dcab5518a3f5c, 0x3c9e85878451255b, 0x4029828119bd34e2,
0x74a05b6f5d3ceccb, 0xb610021542e13eca, 0x0ff979d12f59e2ac, 0x6037da27e4f9cc50,
0x5e92975a0df1847d, 0xd66de190d3e623fe, 0x5032d6b87b568048, 0x9a36b7ce8235216e,
0x80272a7a24f64b4a, 0x93efed8b8c6916f7, 0x37ddbff44cce1555, 0x4b95db5d4b99bd25,
0x92d3fda169812fc0, 0xfb1a4a9a90660bb6, 0x730c196946a4b9b2, 0x81e289aa7f49da68,
0x64669a0f83b1a05f, 0x27b3ff7d9644f48b, 0xcc6b615c8db675b3, 0x674f20b9bcebbe95,
0x6f31238275655982, 0x5ae488713e45cf05, 0xbf619f9954c21157, 0xeabac46040a8eae9,
0x454c6fe9f2c0c1cd, 0x419cf6496412691c, 0xd3dc3bef265b0f70, 0x6d0e60f5c3578a9e,
}
var t4 = [...]uint64{
0x5b0e608526323c55, 0x1a46c1a9fa1b59f5, 0xa9e245a17c4c8ffa, 0x65ca5159db2955d7,
0x05db0a76ce35afc2, 0x81eac77ea9113d45, 0x528ef88ab6ac0a0d, 0xa09ea253597be3ff,
0x430ddfb3ac48cd56, 0xc4b3a67af45ce46f, 0x4ececfd8fbe2d05e, 0x3ef56f10b39935f0,
0x0b22d6829cd619c6, 0x17fd460a74df2069, 0x6cf8cc8e8510ed40, 0xd6c824bf3a6ecaa7,
0x61243d581a817049, 0x048bacb6bbc163a2, 0xd9a38ac27d44cc32, 0x7fddff5baaf410ab,
0xad6d495aa804824b, 0xe1a6a74f2d8c9f94, 0xd4f7851235dee8e3, 0xfd4b7f886540d893,
0x247c20042aa4bfda, 0x096ea1c517d1327c, 0xd56966b4361a6685, 0x277da5c31221057d,
0x94d59893a43acff7, 0x64f0c51ccdc02281, 0x3d33bcc4ff6189db, 0xe005cb184ce66af1,
0xff5ccd1d1db99bea, 0xb0b854a7fe42980f, 0x7bd46a6a718d4b9f, 0xd10fa8cc22a5fd8c,
0xd31484952be4bd31, 0xc7fa975fcb243847, 0x4886ed1e5846c407, 0x28cddb791eb70b04,
0xc2b00be2f573417f, 0x5c9590452180f877, 0x7a6bddfff370eb00, 0xce509e38d6d9d6a4,
0xebeb0f00647fa702, 0x1dcc06cf76606f06, 0xe4d9f28ba286ff0a, 0xd85a305dc918c262,
0x475b1d8732225f54, 0x2d4fb51668ccb5fe, 0xa679b9d9d72bba20, 0x53841c0d912d43a5,
0x3b7eaa48bf12a4e8, 0x781e0e47f22f1ddf, 0xeff20ce60ab50973, 0x20d261d19dffb742,
0x16a12b03062a2e39, 0x1960eb2239650495, 0x251c16fed50eb8b8, 0x9ac0c330f826016e,
0xed152665953e7671, 0x02d63194a6369570, 0x5074f08394b1c987, 0x70ba598c90b25ce1,
0x794a15810b9742f6, 0x0d5925e9fcaf8c6c, 0x3067716cd868744e, 0x910ab077e8d7731b,
0x6a61bbdb5ac42f61, 0x93513efbf0851567, 0xf494724b9e83e9d5, 0xe887e1985c09648d,
0x34b1d3c675370cfd, 0xdc35e433bc0d255d, 0xd0aab84234131be0, 0x08042a50b48b7eaf,
0x9997c4ee44a3ab35, 0x829a7b49201799d0, 0x263b8307b7c54441, 0x752f95f4fd6a6ca6,
0x927217402c08c6e5, 0x2a8ab754a795d9ee, 0xa442f7552f72943d, 0x2c31334e19781208,
0x4fa98d7ceaee6291, 0x55c3862f665db309, 0xbd0610175d53b1f3, 0x46fe6cb840413f27,
0x3fe03792df0cfa59, 0xcfe700372eb85e8f, 0xa7be29e7adbce118, 0xe544ee5cde8431dd,
0x8a781b1b41f1873e, 0xa5c94c78a0d2f0e7, 0x39412e2877b60728, 0xa1265ef3afc9a62c,
0xbcc2770c6a2506c5, 0x3ab66dd5dce1ce12, 0xe65499d04a675b37, 0x7d8f523481bfd216,
0x0f6f64fcec15f389, 0x74efbe618b5b13c8, 0xacdc82b714273e1d, 0xdd40bfe003199d17,
0x37e99257e7e061f8, 0xfa52626904775aaa, 0x8bbbf63a463d56f9, 0xf0013f1543a26e64,
0xa8307e9f879ec898, 0xcc4c27a4150177cc, 0x1b432f2cca1d3348, 0xde1d1f8f9f6fa013,
0x606602a047a7ddd6, 0xd237ab64cc1cb2c7, 0x9b938e7225fcd1d3, 0xec4e03708e0ff476,
0xfeb2fbda3d03c12d, 0xae0bced2ee43889a, 0x22cb8923ebfb4f43, 0x69360d013cf7396d,
0x855e3602d2d4e022, 0x073805bad01f784c, 0x33e17a133852f546, 0xdf4874058ac7b638,
0xba92b29c678aa14a, 0x0ce89fc76cfaadcd, 0x5f9d4e0908339e34, 0xf1afe9291f5923b9,
0x6e3480f60f4a265f, 0xeebf3a2ab29b841c, 0xe21938a88f91b4ad, 0x57dfeff845c6d3c3,
0x2f006b0bf62caaf2, 0x62f479ef6f75ee78, 0x11a55ad41c8916a9, 0xf229d29084fed453,
0x42f1c27b16b000e6, 0x2b1f76749823c074, 0x4b76eca3c2745360, 0x8c98f463b91691bd,
0x14bcc93cf1ade66a, 0x8885213e6d458397, 0x8e177df0274d4711, 0xb49b73b5503f2951,
0x10168168c3f96b6b, 0x0e3d963b63cab0ae, 0x8dfc4b5655a1db14, 0xf789f1356e14de5c,
0x683e68af4e51dac1, 0xc9a84f9d8d4b0fd9, 0x3691e03f52a0f9d1, 0x5ed86e46e1878e80,
0x3c711a0e99d07150, 0x5a0865b20c4e9310, 0x56fbfc1fe4f0682e, 0xea8d5de3105edf9b,
0x71abfdb12379187a, 0x2eb99de1bee77b9c, 0x21ecc0ea33cf4523, 0x59a4d7521805c7a1,
0x3896f5eb56ae7c72, 0xaa638f3db18f75dc, 0x9f39358dabe9808e, 0xb7defa91c00b72ac,
0x6b5541fd62492d92, 0x6dc6dee8f92e4d5b, 0x353f57abc4beea7e, 0x735769d6da5690ce,
0x0a234aa642391484, 0xf6f9508028f80d9d, 0xb8e319a27ab3f215, 0x31ad9c1151341a4d,
0x773c22a57bef5805, 0x45c7561a07968633, 0xf913da9e249dbe36, 0xda652d9b78a64c68,
0x4c27a97f3bc334ef, 0x76621220e66b17f4, 0x967743899acd7d0b, 0xf3ee5bcae0ed6782,
0x409f753600c879fc, 0x06d09a39b5926db6, 0x6f83aeb0317ac588, 0x01e6ca4a86381f21,
0x66ff3462d19f3025, 0x72207c24ddfd3bfb, 0x4af6b6d3e2ece2eb, 0x9c994dbec7ea08de,
0x49ace597b09a8bc4, 0xb38c4766cf0797ba, 0x131b9373c57c2a75, 0xb1822cce61931e58,
0x9d7555b909ba1c0c, 0x127fafdd937d11d2, 0x29da3badc66d92e4, 0xa2c1d57154c2ecbc,
0x58c5134d82f6fe24, 0x1c3ae3515b62274f, 0xe907c82e01cb8126, 0xf8ed091913e37fcb,
0x3249d8f9c80046c9, 0x80cf9bede388fb63, 0x1881539a116cf19e, 0x5103f3f76bd52457,
0x15b7e6f5ae47f7a8, 0xdbd7c6ded47e9ccf, 0x44e55c410228bb1a, 0xb647d4255edb4e99,
0x5d11882bb8aafc30, 0xf5098bbb29d3212a, 0x8fb5ea14e90296b3, 0x677b942157dd025a,
0xfb58e7c0a390acb5, 0x89d3674c83bd4a01, 0x9e2da4df4bf3b93b, 0xfcc41e328cab4829,
0x03f38c96ba582c52, 0xcad1bdbd7fd85db2, 0xbbb442c16082ae83, 0xb95fe86ba5da9ab0,
0xb22e04673771a93f, 0x845358c9493152d8, 0xbe2a488697b4541e, 0x95a2dc2dd38e6966,
0xc02c11ac923c852b, 0x2388b1990df2a87b, 0x7c8008fa1b4f37be, 0x1f70d0c84d54e503,
0x5490adec7ece57d4, 0x002b3c27d9063a3a, 0x7eaea3848030a2bf, 0xc602326ded2003c0,
0x83a7287d69a94086, 0xc57a5fcb30f57a8a, 0xb56844e479ebe779, 0xa373b40f05dcbce9,
0xd71a786e88570ee2, 0x879cbacdbde8f6a0, 0x976ad1bcc164a32f, 0xab21e25e9666d78b,
0x901063aae5e5c33c, 0x9818b34448698d90, 0xe36487ae3e1e8abb, 0xafbdf931893bdcb4,
0x6345a0dc5fbbd519, 0x8628fe269b9465ca, 0x1e5d01603f9c51ec, 0x4de44006a15049b7,
0xbf6c70e5f776cbb1, 0x411218f2ef552bed, 0xcb0c0708705a36a3, 0xe74d14754f986044,
0xcd56d9430ea8280e, 0xc12591d7535f5065, 0xc83223f1720aef96, 0xc3a0396f7363a51f,
}

117
vendor/github.com/cxmcc/tiger/tiger.go generated vendored Normal file
View File

@ -0,0 +1,117 @@
// Package tiger implements the Tiger hash algorithm
// https://github.com/cxmcc/tiger
package tiger
import "hash"
// The size of a Tiger hash value in bytes
const Size = 24
// The blocksize of Tiger hash function in bytes
const BlockSize = 64
const (
chunk = 64
initA = 0x0123456789abcdef
initB = 0xfedcba9876543210
initC = 0xf096a5b4c3b2e187
)
type digest struct {
a uint64
b uint64
c uint64
x [chunk]byte
nx int
length uint64
ver int
}
func (d *digest) Reset() {
d.a = initA
d.b = initB
d.c = initC
d.nx = 0
d.length = 0
}
// New returns a new hash.Hash computing the Tiger hash value
func New() hash.Hash {
d := new(digest)
d.Reset()
d.ver = 1
return d
}
// New returns a new hash.Hash computing the Tiger2 hash value
func New2() hash.Hash {
d := new(digest)
d.Reset()
d.ver = 2
return d
}
func (d *digest) BlockSize() int {
return BlockSize
}
func (d *digest) Size() int {
return Size
}
func (d *digest) Write(p []byte) (length int, err error) {
length = len(p)
d.length += uint64(length)
if d.nx > 0 {
n := len(p)
if n > chunk-d.nx {
n = chunk - d.nx
}
copy(d.x[d.nx:d.nx+n], p[:n])
d.nx += n
if d.nx == chunk {
d.compress(d.x[:chunk])
d.nx = 0
}
p = p[n:]
}
for len(p) >= chunk {
d.compress(p[:chunk])
p = p[chunk:]
}
if len(p) > 0 {
d.nx = copy(d.x[:], p)
}
return
}
func (d digest) Sum(in []byte) []byte {
length := d.length
var tmp [64]byte
if d.ver == 1 {
tmp[0] = 0x01
} else {
tmp[0] = 0x80
}
size := length & 0x3f
if size < 56 {
d.Write(tmp[:56-size])
} else {
d.Write(tmp[:64+56-size])
}
length <<= 3
for i := uint(0); i < 8; i++ {
tmp[i] = byte(length >> (8 * i))
}
d.Write(tmp[:8])
for i := uint(0); i < 8; i++ {
tmp[i] = byte(d.a >> (8 * i))
tmp[i+8] = byte(d.b >> (8 * i))
tmp[i+16] = byte(d.c >> (8 * i))
}
return append(in, tmp[:24]...)
}

7
vendor/github.com/googollee/go-engine.io/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,7 @@
language: go
go: 1.5
install:
- go get "github.com/smartystreets/goconvey/convey"
- go get -v .
script:
- go test -race -v ./...

23
vendor/github.com/googollee/go-engine.io/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2014-2014 Googol Lee <i@googol.im>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

78
vendor/github.com/googollee/go-engine.io/README.md generated vendored Normal file
View File

@ -0,0 +1,78 @@
# go-engine.io
[![GoDoc](http://godoc.org/github.com/googollee/go-engine.io?status.svg)](http://godoc.org/github.com/googollee/go-engine.io) [![Build Status](https://travis-ci.org/googollee/go-engine.io.svg)](https://travis-ci.org/googollee/go-engine.io)
go-engine.io is the implement of engine.io in golang, which is transport-based cross-browser/cross-device bi-directional communication layer for [go-socket.io](https://github.com/googollee/go-socket.io).
It is compatible with node.js implement, and supported long-polling and websocket transport.
## Install
Install the package with:
```bash
go get github.com/googollee/go-engine.io
```
Import it with:
```go
import "github.com/googollee/go-engine.io"
```
and use `engineio` as the package name inside the code.
## Example
Please check example folder for details.
```go
package main
import (
"encoding/hex"
"io/ioutil"
"log"
"net/http"
"github.com/googollee/go-engine.io"
)
func main() {
server, err := engineio.NewServer(nil)
if err != nil {
log.Fatal(err)
}
go func() {
for {
conn, _ := server.Accept()
go func() {
defer conn.Close()
for i := 0; i < 10; i++ {
t, r, _ := conn.NextReader()
b, _ := ioutil.ReadAll(r)
r.Close()
if t == engineio.MessageText {
log.Println(t, string(b))
} else {
log.Println(t, hex.EncodeToString(b))
}
w, _ := conn.NextWriter(t)
w.Write([]byte("pong"))
w.Close()
}
}()
}
}()
http.Handle("/engine.io/", server)
http.Handle("/", http.FileServer(http.Dir("./asset")))
log.Println("Serving at localhost:5000...")
log.Fatal(http.ListenAndServe(":5000", nil))
}
```
## License
The 3-clause BSD License - see LICENSE for more details

50
vendor/github.com/googollee/go-engine.io/ioutil.go generated vendored Normal file
View File

@ -0,0 +1,50 @@
package engineio
import (
"github.com/googollee/go-engine.io/parser"
"io"
"sync"
)
type connReader struct {
*parser.PacketDecoder
closeChan chan struct{}
}
func newConnReader(d *parser.PacketDecoder, closeChan chan struct{}) *connReader {
return &connReader{
PacketDecoder: d,
closeChan: closeChan,
}
}
func (r *connReader) Close() error {
if r.closeChan == nil {
return nil
}
r.closeChan <- struct{}{}
r.closeChan = nil
return nil
}
type connWriter struct {
io.WriteCloser
locker *sync.Mutex
}
func newConnWriter(w io.WriteCloser, locker *sync.Mutex) *connWriter {
return &connWriter{
WriteCloser: w,
locker: locker,
}
}
func (w *connWriter) Close() error {
defer func() {
if w.locker != nil {
w.locker.Unlock()
w.locker = nil
}
}()
return w.WriteCloser.Close()
}

View File

@ -0,0 +1,8 @@
package message
type MessageType int
const (
MessageText MessageType = iota
MessageBinary
)

View File

@ -0,0 +1,45 @@
package parser
import (
"io"
)
type limitReader struct {
io.Reader
remain int
}
func newLimitReader(r io.Reader, limit int) *limitReader {
return &limitReader{
Reader: r,
remain: limit,
}
}
func (r *limitReader) Read(b []byte) (int, error) {
if r.remain == 0 {
return 0, io.EOF
}
if len(b) > r.remain {
b = b[:r.remain]
}
n, err := r.Reader.Read(b)
r.remain -= n
return n, err
}
func (r *limitReader) Close() error {
if r.remain > 0 {
b := make([]byte, 10240)
for {
_, err := r.Read(b)
if err == io.EOF {
break
}
if err != nil {
return err
}
}
}
return nil
}

View File

@ -0,0 +1,191 @@
package parser
import (
"encoding/base64"
"fmt"
"io"
"github.com/googollee/go-engine.io/message"
)
// PacketType is the type of packet
type PacketType string
const (
OPEN PacketType = "open"
CLOSE PacketType = "close"
PING PacketType = "ping"
PONG PacketType = "pong"
MESSAGE PacketType = "message"
UPGRADE PacketType = "upgrade"
NOOP PacketType = "noop"
)
func ByteToType(b byte) (PacketType, error) {
switch b {
case 0:
return OPEN, nil
case 1:
return CLOSE, nil
case 2:
return PING, nil
case 3:
return PONG, nil
case 4:
return MESSAGE, nil
case 5:
return UPGRADE, nil
case 6:
return NOOP, nil
}
return NOOP, fmt.Errorf("invalid byte 0x%x", b)
}
// Byte return the byte of type
func (t PacketType) Byte() byte {
switch t {
case OPEN:
return 0
case CLOSE:
return 1
case PING:
return 2
case PONG:
return 3
case MESSAGE:
return 4
case UPGRADE:
return 5
}
return 6
}
// packetEncoder is the encoder which encode the packet.
type PacketEncoder struct {
closer io.Closer
w io.Writer
}
// NewStringEncoder return the encoder which encode type t to writer w, as string.
func NewStringEncoder(w io.Writer, t PacketType) (*PacketEncoder, error) {
return newEncoder(w, t.Byte()+'0')
}
// NewBinaryEncoder return the encoder which encode type t to writer w, as binary.
func NewBinaryEncoder(w io.Writer, t PacketType) (*PacketEncoder, error) {
return newEncoder(w, t.Byte())
}
func newEncoder(w io.Writer, t byte) (*PacketEncoder, error) {
if _, err := w.Write([]byte{t}); err != nil {
return nil, err
}
closer, ok := w.(io.Closer)
if !ok {
closer = nil
}
return &PacketEncoder{
closer: closer,
w: w,
}, nil
}
// NewB64Encoder return the encoder which encode type t to writer w, as string. When write binary, it uses base64.
func NewB64Encoder(w io.Writer, t PacketType) (*PacketEncoder, error) {
_, err := w.Write([]byte{'b', t.Byte() + '0'})
if err != nil {
return nil, err
}
base := base64.NewEncoder(base64.StdEncoding, w)
return &PacketEncoder{
closer: base,
w: base,
}, nil
}
// Write writes bytes p.
func (e *PacketEncoder) Write(p []byte) (int, error) {
return e.w.Write(p)
}
// Close closes the encoder.
func (e *PacketEncoder) Close() error {
if e.closer != nil {
return e.closer.Close()
}
return nil
}
// packetDecoder is the decoder which decode data to packet.
type PacketDecoder struct {
closer io.Closer
r io.Reader
t PacketType
msgType message.MessageType
}
// NewDecoder return the decoder which decode from reader r.
func NewDecoder(r io.Reader) (*PacketDecoder, error) {
var closer io.Closer
if limit, ok := r.(*limitReader); ok {
closer = limit
}
defer func() {
if closer != nil {
closer.Close()
}
}()
b := []byte{0xff}
if _, err := r.Read(b); err != nil {
return nil, err
}
msgType := message.MessageText
if b[0] == 'b' {
if _, err := r.Read(b); err != nil {
return nil, err
}
r = base64.NewDecoder(base64.StdEncoding, r)
msgType = message.MessageBinary
}
if b[0] >= '0' {
b[0] = b[0] - '0'
} else {
msgType = message.MessageBinary
}
t, err := ByteToType(b[0])
if err != nil {
return nil, err
}
ret := &PacketDecoder{
closer: closer,
r: r,
t: t,
msgType: msgType,
}
closer = nil
return ret, nil
}
// Read reads packet data to bytes p.
func (d *PacketDecoder) Read(p []byte) (int, error) {
return d.r.Read(p)
}
// Type returns the type of packet.
func (d *PacketDecoder) Type() PacketType {
return d.t
}
// MessageType returns the type of message, binary or string.
func (d *PacketDecoder) MessageType() message.MessageType {
return d.msgType
}
// Close closes the decoder.
func (d *PacketDecoder) Close() error {
if d.closer != nil {
return d.closer.Close()
}
return nil
}

View File

@ -0,0 +1,3 @@
package parser
const Protocol = 3

View File

@ -0,0 +1,170 @@
package parser
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
"sync"
)
// payloadEncoder is the encoder to encode packets as payload. It can be used in multi-thread.
type PayloadEncoder struct {
buffers [][]byte
locker sync.Mutex
isString bool
}
// NewStringPayloadEncoder returns the encoder which encode as string.
func NewStringPayloadEncoder() *PayloadEncoder {
return &PayloadEncoder{
isString: true,
}
}
// NewStringPayloadEncoder returns the encoder which encode as binary.
func NewBinaryPayloadEncoder() *PayloadEncoder {
return &PayloadEncoder{
isString: false,
}
}
type encoder struct {
*PacketEncoder
buf *bytes.Buffer
binaryPrefix string
payload *PayloadEncoder
}
func (e encoder) Close() error {
if err := e.PacketEncoder.Close(); err != nil {
return err
}
var buffer []byte
if e.payload.isString {
buffer = []byte(fmt.Sprintf("%d:%s", e.buf.Len(), e.buf.String()))
} else {
buffer = []byte(fmt.Sprintf("%s%d", e.binaryPrefix, e.buf.Len()))
for i, n := 0, len(buffer); i < n; i++ {
buffer[i] = buffer[i] - '0'
}
buffer = append(buffer, 0xff)
buffer = append(buffer, e.buf.Bytes()...)
}
e.payload.locker.Lock()
e.payload.buffers = append(e.payload.buffers, buffer)
e.payload.locker.Unlock()
return nil
}
// NextString returns the encoder with packet type t and encode as string.
func (e *PayloadEncoder) NextString(t PacketType) (io.WriteCloser, error) {
buf := bytes.NewBuffer(nil)
pEncoder, err := NewStringEncoder(buf, t)
if err != nil {
return nil, err
}
return encoder{
PacketEncoder: pEncoder,
buf: buf,
binaryPrefix: "0",
payload: e,
}, nil
}
// NextBinary returns the encoder with packet type t and encode as binary.
func (e *PayloadEncoder) NextBinary(t PacketType) (io.WriteCloser, error) {
buf := bytes.NewBuffer(nil)
var pEncoder *PacketEncoder
var err error
if e.isString {
pEncoder, err = NewB64Encoder(buf, t)
} else {
pEncoder, err = NewBinaryEncoder(buf, t)
}
if err != nil {
return nil, err
}
return encoder{
PacketEncoder: pEncoder,
buf: buf,
binaryPrefix: "1",
payload: e,
}, nil
}
// EncodeTo writes encoded payload to writer w. It will clear the buffer of encoder.
func (e *PayloadEncoder) EncodeTo(w io.Writer) error {
e.locker.Lock()
buffers := e.buffers
e.buffers = nil
e.locker.Unlock()
for _, b := range buffers {
for len(b) > 0 {
n, err := w.Write(b)
if err != nil {
return err
}
b = b[n:]
}
}
return nil
}
//IsString returns true if payload encode to string, otherwise returns false.
func (e *PayloadEncoder) IsString() bool {
return e.isString
}
// payloadDecoder is the decoder to decode payload.
type PayloadDecoder struct {
r *bufio.Reader
}
// NewPaylaodDecoder returns the payload decoder which read from reader r.
func NewPayloadDecoder(r io.Reader) *PayloadDecoder {
br, ok := r.(*bufio.Reader)
if !ok {
br = bufio.NewReader(r)
}
return &PayloadDecoder{
r: br,
}
}
// Next returns the packet decoder. Make sure it will be closed after used.
func (d *PayloadDecoder) Next() (*PacketDecoder, error) {
firstByte, err := d.r.Peek(1)
if err != nil {
return nil, err
}
isBinary := firstByte[0] < '0'
delim := byte(':')
if isBinary {
d.r.ReadByte()
delim = 0xff
}
line, err := d.r.ReadBytes(delim)
if err != nil {
return nil, err
}
l := len(line)
if l < 1 {
return nil, fmt.Errorf("invalid input")
}
lenByte := line[:l-1]
if isBinary {
for i, n := 0, l; i < n; i++ {
line[i] = line[i] + '0'
}
}
packetLen, err := strconv.ParseInt(string(lenByte), 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid input")
}
return NewDecoder(newLimitReader(d.r, int(packetLen)))
}

View File

@ -0,0 +1,149 @@
package polling
import (
"bytes"
"fmt"
"github.com/googollee/go-engine.io/message"
"io"
"io/ioutil"
"net/http"
"net/url"
"time"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
)
type client struct {
req http.Request
url url.URL
seq uint
getResp *http.Response
postResp *http.Response
resp *http.Response
payloadDecoder *parser.PayloadDecoder
payloadEncoder *parser.PayloadEncoder
client *http.Client
state state
}
func NewClient(r *http.Request) (transport.Client, error) {
newEncoder := parser.NewBinaryPayloadEncoder
if _, ok := r.URL.Query()["b64"]; ok {
newEncoder = parser.NewStringPayloadEncoder
}
ret := &client{
req: *r,
url: *r.URL,
seq: 0,
payloadEncoder: newEncoder(),
client: http.DefaultClient,
state: stateNormal,
}
return ret, nil
}
func (c *client) Response() *http.Response {
return c.resp
}
func (c *client) NextReader() (*parser.PacketDecoder, error) {
if c.state != stateNormal {
return nil, io.EOF
}
if c.payloadDecoder != nil {
ret, err := c.payloadDecoder.Next()
if err != io.EOF {
return ret, err
}
c.getResp.Body.Close()
c.payloadDecoder = nil
}
req := c.getReq()
req.Method = "GET"
var err error
c.getResp, err = c.client.Do(req)
if err != nil {
return nil, err
}
if c.resp == nil {
c.resp = c.getResp
}
c.payloadDecoder = parser.NewPayloadDecoder(c.getResp.Body)
return c.payloadDecoder.Next()
}
func (c *client) NextWriter(messageType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error) {
if c.state != stateNormal {
return nil, io.EOF
}
next := c.payloadEncoder.NextBinary
if messageType == message.MessageText {
next = c.payloadEncoder.NextString
}
w, err := next(packetType)
if err != nil {
return nil, err
}
return newClientWriter(c, w), nil
}
func (c *client) Close() error {
if c.state != stateNormal {
return nil
}
c.state = stateClosed
return nil
}
func (c *client) getReq() *http.Request {
req := c.req
url := c.url
req.URL = &url
query := req.URL.Query()
query.Set("t", fmt.Sprintf("%d-%d", time.Now().Unix()*1000, c.seq))
c.seq++
req.URL.RawQuery = query.Encode()
return &req
}
func (c *client) doPost() error {
if c.state != stateNormal {
return io.EOF
}
req := c.getReq()
req.Method = "POST"
buf := bytes.NewBuffer(nil)
if err := c.payloadEncoder.EncodeTo(buf); err != nil {
return err
}
req.Body = ioutil.NopCloser(buf)
var err error
c.postResp, err = c.client.Do(req)
if err != nil {
return err
}
if c.resp == nil {
c.resp = c.postResp
}
return nil
}
type clientWriter struct {
io.WriteCloser
client *client
}
func newClientWriter(c *client, w io.WriteCloser) io.WriteCloser {
return &clientWriter{
WriteCloser: w,
client: c,
}
}
func (w *clientWriter) Close() error {
if err := w.WriteCloser.Close(); err != nil {
return err
}
return w.client.doPost()
}

View File

@ -0,0 +1,197 @@
package polling
import (
"bytes"
"html/template"
"io"
"net/http"
"sync"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
)
type state int
const (
stateUnknow state = iota
stateNormal
stateClosing
stateClosed
)
type Polling struct {
sendChan chan bool
encoder *parser.PayloadEncoder
callback transport.Callback
getLocker *Locker
postLocker *Locker
state state
stateLocker sync.Mutex
}
func NewServer(w http.ResponseWriter, r *http.Request, callback transport.Callback) (transport.Server, error) {
newEncoder := parser.NewBinaryPayloadEncoder
if r.URL.Query()["b64"] != nil {
newEncoder = parser.NewStringPayloadEncoder
}
ret := &Polling{
sendChan: MakeSendChan(),
encoder: newEncoder(),
callback: callback,
getLocker: NewLocker(),
postLocker: NewLocker(),
state: stateNormal,
}
return ret, nil
}
func (p *Polling) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
p.get(w, r)
case "POST":
p.post(w, r)
}
}
func (p *Polling) Close() error {
if p.getState() != stateNormal {
return nil
}
close(p.sendChan)
p.setState(stateClosing)
if p.getLocker.TryLock() {
if p.postLocker.TryLock() {
p.callback.OnClose(p)
p.setState(stateClosed)
p.postLocker.Unlock()
}
p.getLocker.Unlock()
}
return nil
}
func (p *Polling) NextWriter(msgType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error) {
if p.getState() != stateNormal {
return nil, io.EOF
}
var ret io.WriteCloser
var err error
switch msgType {
case message.MessageText:
ret, err = p.encoder.NextString(packetType)
case message.MessageBinary:
ret, err = p.encoder.NextBinary(packetType)
}
if err != nil {
return nil, err
}
return NewWriter(ret, p), nil
}
func (p *Polling) get(w http.ResponseWriter, r *http.Request) {
if !p.getLocker.TryLock() {
http.Error(w, "overlay get", http.StatusBadRequest)
return
}
if p.getState() != stateNormal {
http.Error(w, "closed", http.StatusBadRequest)
return
}
defer func() {
if p.getState() == stateClosing {
if p.postLocker.TryLock() {
p.setState(stateClosed)
p.callback.OnClose(p)
p.postLocker.Unlock()
}
}
p.getLocker.Unlock()
}()
<-p.sendChan
if j := r.URL.Query().Get("j"); j != "" {
// JSONP Polling
w.Header().Set("Content-Type", "text/javascript; charset=UTF-8")
tmp := bytes.Buffer{}
p.encoder.EncodeTo(&tmp)
pl := template.JSEscapeString(tmp.String())
w.Write([]byte("___eio[" + j + "](\""))
w.Write([]byte(pl))
w.Write([]byte("\");"))
} else {
// XHR Polling
if p.encoder.IsString() {
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
} else {
w.Header().Set("Content-Type", "application/octet-stream")
}
p.encoder.EncodeTo(w)
}
}
func (p *Polling) post(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
if !p.postLocker.TryLock() {
http.Error(w, "overlay post", http.StatusBadRequest)
return
}
if p.getState() != stateNormal {
http.Error(w, "closed", http.StatusBadRequest)
return
}
defer func() {
if p.getState() == stateClosing {
if p.getLocker.TryLock() {
p.setState(stateClosed)
p.callback.OnClose(p)
p.getLocker.Unlock()
}
}
p.postLocker.Unlock()
}()
var decoder *parser.PayloadDecoder
if j := r.URL.Query().Get("j"); j != "" {
// JSONP Polling
d := r.FormValue("d")
decoder = parser.NewPayloadDecoder(bytes.NewBufferString(d))
} else {
// XHR Polling
decoder = parser.NewPayloadDecoder(r.Body)
}
for {
d, err := decoder.Next()
if err == io.EOF {
break
}
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
p.callback.OnPacket(d)
d.Close()
}
w.Write([]byte("ok"))
}
func (p *Polling) setState(s state) {
p.stateLocker.Lock()
defer p.stateLocker.Unlock()
p.state = s
}
func (p *Polling) getState() state {
p.stateLocker.Lock()
defer p.stateLocker.Unlock()
return p.state
}

View File

@ -0,0 +1,28 @@
package polling
type Locker struct {
locker chan struct{}
}
func NewLocker() *Locker {
return &Locker{
locker: make(chan struct{}, 1),
}
}
func (l *Locker) Lock() {
l.locker <- struct{}{}
}
func (l *Locker) TryLock() bool {
select {
case l.locker <- struct{}{}:
return true
default:
return false
}
}
func (l *Locker) Unlock() {
<-l.locker
}

View File

@ -0,0 +1,33 @@
package polling
import (
"errors"
"io"
)
func MakeSendChan() chan bool {
return make(chan bool, 1)
}
type Writer struct {
io.WriteCloser
server *Polling
}
func NewWriter(w io.WriteCloser, server *Polling) *Writer {
return &Writer{
WriteCloser: w,
server: server,
}
}
func (w *Writer) Close() error {
if w.server.getState() != stateNormal {
return errors.New("use of closed network connection")
}
select {
case w.server.sendChan <- true:
default:
}
return w.WriteCloser.Close()
}

View File

@ -0,0 +1,12 @@
package polling
import (
"github.com/googollee/go-engine.io/transport"
)
var Creater = transport.Creater{
Name: "polling",
Upgrading: false,
Server: NewServer,
Client: NewClient,
}

188
vendor/github.com/googollee/go-engine.io/server.go generated vendored Normal file
View File

@ -0,0 +1,188 @@
package engineio
import (
"bytes"
"crypto/md5"
"encoding/base64"
"fmt"
"net/http"
"sync/atomic"
"time"
"github.com/googollee/go-engine.io/polling"
"github.com/googollee/go-engine.io/websocket"
)
type config struct {
PingTimeout time.Duration
PingInterval time.Duration
MaxConnection int
AllowRequest func(*http.Request) error
AllowUpgrades bool
Cookie string
NewId func(r *http.Request) string
}
// Server is the server of engine.io.
type Server struct {
config config
socketChan chan Conn
serverSessions Sessions
creaters transportCreaters
currentConnection int32
}
// NewServer returns the server suppported given transports. If transports is nil, server will use ["polling", "websocket"] as default.
func NewServer(transports []string) (*Server, error) {
if transports == nil {
transports = []string{"polling", "websocket"}
}
creaters := make(transportCreaters)
for _, t := range transports {
switch t {
case "polling":
creaters[t] = polling.Creater
case "websocket":
creaters[t] = websocket.Creater
default:
return nil, InvalidError
}
}
return &Server{
config: config{
PingTimeout: 60000 * time.Millisecond,
PingInterval: 25000 * time.Millisecond,
MaxConnection: 1000,
AllowRequest: func(*http.Request) error { return nil },
AllowUpgrades: true,
Cookie: "io",
NewId: newId,
},
socketChan: make(chan Conn),
serverSessions: newServerSessions(),
creaters: creaters,
}, nil
}
// SetPingTimeout sets the timeout of ping. When time out, server will close connection. Default is 60s.
func (s *Server) SetPingTimeout(t time.Duration) {
s.config.PingTimeout = t
}
// SetPingInterval sets the interval of ping. Default is 25s.
func (s *Server) SetPingInterval(t time.Duration) {
s.config.PingInterval = t
}
// SetMaxConnection sets the max connetion. Default is 1000.
func (s *Server) SetMaxConnection(n int) {
s.config.MaxConnection = n
}
// GetMaxConnection returns the current max connection
func (s *Server) GetMaxConnection() int {
return s.config.MaxConnection
}
// Count returns a count of current number of active connections in session
func (s *Server) Count() int {
return int(atomic.LoadInt32(&s.currentConnection))
}
// SetAllowRequest sets the middleware function when establish connection. If it return non-nil, connection won't be established. Default will allow all request.
func (s *Server) SetAllowRequest(f func(*http.Request) error) {
s.config.AllowRequest = f
}
// SetAllowUpgrades sets whether server allows transport upgrade. Default is true.
func (s *Server) SetAllowUpgrades(allow bool) {
s.config.AllowUpgrades = allow
}
// SetCookie sets the name of cookie which used by engine.io. Default is "io".
func (s *Server) SetCookie(prefix string) {
s.config.Cookie = prefix
}
// SetNewId sets the callback func to generate new connection id. By default, id is generated from remote addr + current time stamp
func (s *Server) SetNewId(f func(*http.Request) string) {
s.config.NewId = f
}
// SetSessionManager sets the sessions as server's session manager. Default sessions is single process manager. You can custom it as load balance.
func (s *Server) SetSessionManager(sessions Sessions) {
s.serverSessions = sessions
}
// ServeHTTP handles http request.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
sid := r.URL.Query().Get("sid")
conn := s.serverSessions.Get(sid)
if conn == nil {
if sid != "" {
http.Error(w, "invalid sid", http.StatusBadRequest)
return
}
if err := s.config.AllowRequest(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
n := atomic.AddInt32(&s.currentConnection, 1)
if int(n) > s.config.MaxConnection {
atomic.AddInt32(&s.currentConnection, -1)
http.Error(w, "too many connections", http.StatusServiceUnavailable)
return
}
sid = s.config.NewId(r)
var err error
conn, err = newServerConn(sid, w, r, s)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
s.serverSessions.Set(sid, conn)
s.socketChan <- conn
}
http.SetCookie(w, &http.Cookie{
Name: s.config.Cookie,
Value: sid,
})
conn.(*serverConn).ServeHTTP(w, r)
}
// Accept returns Conn when client connect to server.
func (s *Server) Accept() (Conn, error) {
return <-s.socketChan, nil
}
func (s *Server) configure() config {
return s.config
}
func (s *Server) transports() transportCreaters {
return s.creaters
}
func (s *Server) onClose(id string) {
s.serverSessions.Remove(id)
atomic.AddInt32(&s.currentConnection, -1)
}
func newId(r *http.Request) string {
hash := fmt.Sprintf("%s %s", r.RemoteAddr, time.Now())
buf := bytes.NewBuffer(nil)
sum := md5.Sum([]byte(hash))
encoder := base64.NewEncoder(base64.URLEncoding, buf)
encoder.Write(sum[:])
encoder.Close()
return buf.String()[:20]
}

388
vendor/github.com/googollee/go-engine.io/server_conn.go generated vendored Normal file
View File

@ -0,0 +1,388 @@
package engineio
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"sync"
"time"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
)
type MessageType message.MessageType
const (
MessageBinary MessageType = MessageType(message.MessageBinary)
MessageText MessageType = MessageType(message.MessageText)
)
// Conn is the connection object of engine.io.
type Conn interface {
// Id returns the session id of connection.
Id() string
// Request returns the first http request when established connection.
Request() *http.Request
// Close closes the connection.
Close() error
// NextReader returns the next message type, reader. If no message received, it will block.
NextReader() (MessageType, io.ReadCloser, error)
// NextWriter returns the next message writer with given message type.
NextWriter(messageType MessageType) (io.WriteCloser, error)
}
type transportCreaters map[string]transport.Creater
func (c transportCreaters) Get(name string) transport.Creater {
return c[name]
}
type serverCallback interface {
configure() config
transports() transportCreaters
onClose(sid string)
}
type state int
const (
stateUnknow state = iota
stateNormal
stateUpgrading
stateClosing
stateClosed
)
type serverConn struct {
id string
request *http.Request
callback serverCallback
writerLocker sync.Mutex
transportLocker sync.RWMutex
currentName string
current transport.Server
upgradingName string
upgrading transport.Server
state state
stateLocker sync.RWMutex
readerChan chan *connReader
pingTimeout time.Duration
pingInterval time.Duration
pingChan chan bool
pingLocker sync.Mutex
}
var InvalidError = errors.New("invalid transport")
func newServerConn(id string, w http.ResponseWriter, r *http.Request, callback serverCallback) (*serverConn, error) {
transportName := r.URL.Query().Get("transport")
creater := callback.transports().Get(transportName)
if creater.Name == "" {
return nil, InvalidError
}
ret := &serverConn{
id: id,
request: r,
callback: callback,
state: stateNormal,
readerChan: make(chan *connReader),
pingTimeout: callback.configure().PingTimeout,
pingInterval: callback.configure().PingInterval,
pingChan: make(chan bool),
}
transport, err := creater.Server(w, r, ret)
if err != nil {
return nil, err
}
ret.setCurrent(transportName, transport)
if err := ret.onOpen(); err != nil {
return nil, err
}
go ret.pingLoop()
return ret, nil
}
func (c *serverConn) Id() string {
return c.id
}
func (c *serverConn) Request() *http.Request {
return c.request
}
func (c *serverConn) NextReader() (MessageType, io.ReadCloser, error) {
if c.getState() == stateClosed {
return MessageBinary, nil, io.EOF
}
ret := <-c.readerChan
if ret == nil {
return MessageBinary, nil, io.EOF
}
return MessageType(ret.MessageType()), ret, nil
}
func (c *serverConn) NextWriter(t MessageType) (io.WriteCloser, error) {
switch c.getState() {
case stateUpgrading:
for i := 0; i < 30; i++ {
time.Sleep(50 * time.Millisecond)
if c.getState() != stateUpgrading {
break
}
}
if c.getState() == stateUpgrading {
return nil, fmt.Errorf("upgrading")
}
case stateNormal:
default:
return nil, io.EOF
}
c.writerLocker.Lock()
ret, err := c.getCurrent().NextWriter(message.MessageType(t), parser.MESSAGE)
if err != nil {
c.writerLocker.Unlock()
return ret, err
}
writer := newConnWriter(ret, &c.writerLocker)
return writer, err
}
func (c *serverConn) Close() error {
if c.getState() != stateNormal && c.getState() != stateUpgrading {
return nil
}
if c.upgrading != nil {
c.upgrading.Close()
}
c.writerLocker.Lock()
if w, err := c.getCurrent().NextWriter(message.MessageText, parser.CLOSE); err == nil {
writer := newConnWriter(w, &c.writerLocker)
writer.Close()
} else {
c.writerLocker.Unlock()
}
if err := c.getCurrent().Close(); err != nil {
return err
}
c.setState(stateClosing)
return nil
}
func (c *serverConn) ServeHTTP(w http.ResponseWriter, r *http.Request) {
transportName := r.URL.Query().Get("transport")
if c.currentName != transportName {
creater := c.callback.transports().Get(transportName)
if creater.Name == "" {
http.Error(w, fmt.Sprintf("invalid transport %s", transportName), http.StatusBadRequest)
return
}
u, err := creater.Server(w, r, c)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
c.setUpgrading(creater.Name, u)
return
}
c.current.ServeHTTP(w, r)
}
func (c *serverConn) OnPacket(r *parser.PacketDecoder) {
if s := c.getState(); s != stateNormal && s != stateUpgrading {
return
}
switch r.Type() {
case parser.OPEN:
case parser.CLOSE:
c.getCurrent().Close()
case parser.PING:
c.writerLocker.Lock()
t := c.getCurrent()
u := c.getUpgrade()
newWriter := t.NextWriter
if u != nil {
if w, _ := t.NextWriter(message.MessageText, parser.NOOP); w != nil {
w.Close()
}
newWriter = u.NextWriter
}
if w, _ := newWriter(message.MessageText, parser.PONG); w != nil {
io.Copy(w, r)
w.Close()
}
c.writerLocker.Unlock()
fallthrough
case parser.PONG:
c.pingLocker.Lock()
defer c.pingLocker.Unlock()
if s := c.getState(); s != stateNormal && s != stateUpgrading {
return
}
c.pingChan <- true
case parser.MESSAGE:
closeChan := make(chan struct{})
c.readerChan <- newConnReader(r, closeChan)
<-closeChan
close(closeChan)
r.Close()
case parser.UPGRADE:
c.upgraded()
case parser.NOOP:
}
}
func (c *serverConn) OnClose(server transport.Server) {
if t := c.getUpgrade(); server == t {
c.setUpgrading("", nil)
t.Close()
return
}
t := c.getCurrent()
if server != t {
return
}
t.Close()
if t := c.getUpgrade(); t != nil {
t.Close()
c.setUpgrading("", nil)
}
c.setState(stateClosed)
close(c.readerChan)
c.pingLocker.Lock()
close(c.pingChan)
c.pingLocker.Unlock()
c.callback.onClose(c.id)
}
func (s *serverConn) onOpen() error {
upgrades := []string{}
for name := range s.callback.transports() {
if name == s.currentName {
continue
}
upgrades = append(upgrades, name)
}
type connectionInfo struct {
Sid string `json:"sid"`
Upgrades []string `json:"upgrades"`
PingInterval time.Duration `json:"pingInterval"`
PingTimeout time.Duration `json:"pingTimeout"`
}
resp := connectionInfo{
Sid: s.Id(),
Upgrades: upgrades,
PingInterval: s.callback.configure().PingInterval / time.Millisecond,
PingTimeout: s.callback.configure().PingTimeout / time.Millisecond,
}
w, err := s.getCurrent().NextWriter(message.MessageText, parser.OPEN)
if err != nil {
return err
}
encoder := json.NewEncoder(w)
if err := encoder.Encode(resp); err != nil {
return err
}
if err := w.Close(); err != nil {
return err
}
return nil
}
func (c *serverConn) getCurrent() transport.Server {
c.transportLocker.RLock()
defer c.transportLocker.RUnlock()
return c.current
}
func (c *serverConn) getUpgrade() transport.Server {
c.transportLocker.RLock()
defer c.transportLocker.RUnlock()
return c.upgrading
}
func (c *serverConn) setCurrent(name string, s transport.Server) {
c.transportLocker.Lock()
defer c.transportLocker.Unlock()
c.currentName = name
c.current = s
}
func (c *serverConn) setUpgrading(name string, s transport.Server) {
c.transportLocker.Lock()
defer c.transportLocker.Unlock()
c.upgradingName = name
c.upgrading = s
c.setState(stateUpgrading)
}
func (c *serverConn) upgraded() {
c.transportLocker.Lock()
current := c.current
c.current = c.upgrading
c.currentName = c.upgradingName
c.upgrading = nil
c.upgradingName = ""
c.transportLocker.Unlock()
current.Close()
c.setState(stateNormal)
}
func (c *serverConn) getState() state {
c.stateLocker.RLock()
defer c.stateLocker.RUnlock()
return c.state
}
func (c *serverConn) setState(state state) {
c.stateLocker.Lock()
defer c.stateLocker.Unlock()
c.state = state
}
func (c *serverConn) pingLoop() {
lastPing := time.Now()
lastTry := lastPing
for {
now := time.Now()
pingDiff := now.Sub(lastPing)
tryDiff := now.Sub(lastTry)
select {
case ok := <-c.pingChan:
if !ok {
return
}
lastPing = time.Now()
lastTry = lastPing
case <-time.After(c.pingInterval - tryDiff):
c.writerLocker.Lock()
if w, _ := c.getCurrent().NextWriter(message.MessageText, parser.PING); w != nil {
writer := newConnWriter(w, &c.writerLocker)
writer.Close()
} else {
c.writerLocker.Unlock()
}
lastTry = time.Now()
case <-time.After(c.pingTimeout - pingDiff):
c.Close()
return
}
}
}

47
vendor/github.com/googollee/go-engine.io/sessions.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package engineio
import (
"sync"
)
type Sessions interface {
Get(id string) Conn
Set(id string, conn Conn)
Remove(id string)
}
type serverSessions struct {
sessions map[string]Conn
locker sync.RWMutex
}
func newServerSessions() *serverSessions {
return &serverSessions{
sessions: make(map[string]Conn),
}
}
func (s *serverSessions) Get(id string) Conn {
s.locker.RLock()
defer s.locker.RUnlock()
ret, ok := s.sessions[id]
if !ok {
return nil
}
return ret
}
func (s *serverSessions) Set(id string, conn Conn) {
s.locker.Lock()
defer s.locker.Unlock()
s.sessions[id] = conn
}
func (s *serverSessions) Remove(id string) {
s.locker.Lock()
defer s.locker.Unlock()
delete(s.sessions, id)
}

View File

@ -0,0 +1,50 @@
package transport
import (
"io"
"net/http"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
)
type Callback interface {
OnPacket(r *parser.PacketDecoder)
OnClose(server Server)
}
type Creater struct {
Name string
Upgrading bool
Server func(w http.ResponseWriter, r *http.Request, callback Callback) (Server, error)
Client func(r *http.Request) (Client, error)
}
// Server is a transport layer in server to connect client.
type Server interface {
// ServeHTTP handles the http request. It will call conn.onPacket when receive packet.
ServeHTTP(http.ResponseWriter, *http.Request)
// Close closes the transport.
Close() error
// NextWriter returns packet writer. This function call should be synced.
NextWriter(messageType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error)
}
// Client is a transport layer in client to connect server.
type Client interface {
// Response returns the response of last http request.
Response() *http.Response
// NextReader returns packet decoder. This function call should be synced.
NextReader() (*parser.PacketDecoder, error)
// NextWriter returns packet writer. This function call should be synced.
NextWriter(messageType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error)
// Close closes the transport.
Close() error
}

View File

@ -0,0 +1,72 @@
package websocket
import (
"io"
"net/http"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
"github.com/gorilla/websocket"
)
type client struct {
conn *websocket.Conn
resp *http.Response
}
func NewClient(r *http.Request) (transport.Client, error) {
dialer := websocket.DefaultDialer
conn, resp, err := dialer.Dial(r.URL.String(), r.Header)
if err != nil {
return nil, err
}
return &client{
conn: conn,
resp: resp,
}, nil
}
func (c *client) Response() *http.Response {
return c.resp
}
func (c *client) NextReader() (*parser.PacketDecoder, error) {
var reader io.Reader
for {
t, r, err := c.conn.NextReader()
if err != nil {
return nil, err
}
switch t {
case websocket.TextMessage:
fallthrough
case websocket.BinaryMessage:
reader = r
return parser.NewDecoder(reader)
}
}
}
func (c *client) NextWriter(msgType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error) {
wsType, newEncoder := websocket.TextMessage, parser.NewStringEncoder
if msgType == message.MessageBinary {
wsType, newEncoder = websocket.BinaryMessage, parser.NewBinaryEncoder
}
w, err := c.conn.NextWriter(wsType)
if err != nil {
return nil, err
}
ret, err := newEncoder(w, packetType)
if err != nil {
return nil, err
}
return ret, nil
}
func (c *client) Close() error {
return c.conn.Close()
}

View File

@ -0,0 +1,81 @@
package websocket
import (
"io"
"net/http"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
"github.com/gorilla/websocket"
)
type Server struct {
callback transport.Callback
conn *websocket.Conn
}
func NewServer(w http.ResponseWriter, r *http.Request, callback transport.Callback) (transport.Server, error) {
conn, err := websocket.Upgrade(w, r, nil, 10240, 10240)
if err != nil {
return nil, err
}
ret := &Server{
callback: callback,
conn: conn,
}
go ret.serveHTTP(w, r)
return ret, nil
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}
func (s *Server) NextWriter(msgType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error) {
wsType, newEncoder := websocket.TextMessage, parser.NewStringEncoder
if msgType == message.MessageBinary {
wsType, newEncoder = websocket.BinaryMessage, parser.NewBinaryEncoder
}
w, err := s.conn.NextWriter(wsType)
if err != nil {
return nil, err
}
ret, err := newEncoder(w, packetType)
if err != nil {
return nil, err
}
return ret, nil
}
func (s *Server) Close() error {
return s.conn.Close()
}
func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) {
defer s.callback.OnClose(s)
for {
t, r, err := s.conn.NextReader()
if err != nil {
s.conn.Close()
return
}
switch t {
case websocket.TextMessage:
fallthrough
case websocket.BinaryMessage:
decoder, err := parser.NewDecoder(r)
if err != nil {
return
}
s.callback.OnPacket(decoder)
decoder.Close()
}
}
}

View File

@ -0,0 +1,12 @@
package websocket
import (
"github.com/googollee/go-engine.io/transport"
)
var Creater = transport.Creater{
Name: "websocket",
Upgrading: true,
Server: NewServer,
Client: NewClient,
}

5
vendor/github.com/googollee/go-socket.io/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,5 @@
language: go
go: 1.5
install:
- go get "github.com/smartystreets/goconvey/convey"
- go get -v .

23
vendor/github.com/googollee/go-socket.io/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2014-2014 Googol Lee <i@googol.im>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

127
vendor/github.com/googollee/go-socket.io/README.md generated vendored Normal file
View File

@ -0,0 +1,127 @@
# socket.io
[![GoDoc](http://godoc.org/github.com/googollee/go-socket.io?status.svg)](http://godoc.org/github.com/googollee/go-socket.io) [![Build Status](https://travis-ci.org/googollee/go-socket.io.svg)](https://travis-ci.org/googollee/go-socket.io)
**Please use v1.4 branch, or import "gopkg.in/googollee/go-socket.io.v1". I have no time to maintain master branch now**
go-socket.io is an implementation of [socket.io](http://socket.io) in golang, which is a realtime application framework.
It is compatible with latest implementation of socket.io in node.js, and supports room and namespace.
* for compatability with socket.io 0.9.x, please use branch 0.9.x *
## Install
Install the package with:
```bash
go get github.com/googollee/go-socket.io
```
Import it with:
```go
import "github.com/googollee/go-socket.io"
```
and use `socketio` as the package name inside the code.
## Example
Please check the example folder for details.
```go
package main
import (
"log"
"net/http"
"github.com/googollee/go-socket.io"
)
func main() {
server, err := socketio.NewServer(nil)
if err != nil {
log.Fatal(err)
}
server.On("connection", func(so socketio.Socket) {
log.Println("on connection")
so.Join("chat")
so.On("chat message", func(msg string) {
log.Println("emit:", so.Emit("chat message", msg))
so.BroadcastTo("chat", "chat message", msg)
})
so.On("disconnection", func() {
log.Println("on disconnect")
})
})
server.On("error", func(so socketio.Socket, err error) {
log.Println("error:", err)
})
http.Handle("/socket.io/", server)
http.Handle("/", http.FileServer(http.Dir("./asset")))
log.Println("Serving at localhost:5000...")
log.Fatal(http.ListenAndServe(":5000", nil))
}
```
## Acknowledgements in go-socket.io 1.X.X
[See documentation about acknowledgements](http://socket.io/docs/#sending-and-getting-data-(acknowledgements))
##### Sending ACK with data from SERVER to CLIENT
* Client-side
```javascript
//using client-side socket.io-1.X.X.js
socket.emit('some:event', JSON.stringify(someData), function(data){
console.log('ACK from server wtih data: ', data));
});
```
* Server-side
```go
// The return type may vary depending on whether you will return
// In golang implementation of socket.io don't used callbacks for acknowledgement,
// but used return value, which wrapped into ack package and returned to the client's callback in JavaScript
so.On("some:event", func(msg string) string {
return msg //Sending ack with data in msg back to client, using "return statement"
})
```
##### Sending ACK with data from CLIENT to SERVER
* Client-side
```javascript
//using client-side socket.io-1.X.X.js
//last parameter of "on" handler is callback for sending ack to server with data or without data
socket.on('some:event', function (msg, sendAckCb) {
//Sending ACK with data to server after receiving some:event from server
sendAckCb(JSON.stringify(data)); // for example used serializing to JSON
}
```
* Server-side
```go
//You can use Emit or BroadcastTo with last parameter as callback for handling ack from client
//Sending packet to room "room_name" and event "some:event"
so.BroadcastTo("room_name", "some:event", dataForClient, func (so socketio.Socket, data string) {
log.Println("Client ACK with data: ", data)
})
// Or
so.Emit("some:event", dataForClient, func (so socketio.Socket, data string) {
log.Println("Client ACK with data: ", data)
})
```
## License
The 3-clause BSD License - see LICENSE for more details

70
vendor/github.com/googollee/go-socket.io/adapter.go generated vendored Normal file
View File

@ -0,0 +1,70 @@
package socketio
import "sync"
// BroadcastAdaptor is the adaptor to handle broadcasts.
type BroadcastAdaptor interface {
// Join causes the socket to join a room.
Join(room string, socket Socket) error
// Leave causes the socket to leave a room.
Leave(room string, socket Socket) error
// Send will send an event with args to the room. If "ignore" is not nil, the event will be excluded from being sent to "ignore".
Send(ignore Socket, room, event string, args ...interface{}) error
}
var newBroadcast = newBroadcastDefault
type broadcast struct {
m map[string]map[string]Socket
sync.RWMutex
}
func newBroadcastDefault() BroadcastAdaptor {
return &broadcast{
m: make(map[string]map[string]Socket),
}
}
func (b *broadcast) Join(room string, socket Socket) error {
b.Lock()
sockets, ok := b.m[room]
if !ok {
sockets = make(map[string]Socket)
}
sockets[socket.Id()] = socket
b.m[room] = sockets
b.Unlock()
return nil
}
func (b *broadcast) Leave(room string, socket Socket) error {
b.Lock()
defer b.Unlock()
sockets, ok := b.m[room]
if !ok {
return nil
}
delete(sockets, socket.Id())
if len(sockets) == 0 {
delete(b.m, room)
return nil
}
b.m[room] = sockets
return nil
}
func (b *broadcast) Send(ignore Socket, room, event string, args ...interface{}) error {
b.RLock()
sockets := b.m[room]
for id, s := range sockets {
if ignore != nil && ignore.Id() == id {
continue
}
s.Emit(event, args...)
}
b.RUnlock()
return nil
}

168
vendor/github.com/googollee/go-socket.io/attachment.go generated vendored Normal file
View File

@ -0,0 +1,168 @@
package socketio
import (
"bytes"
"encoding/json"
"fmt"
"io"
"reflect"
)
// Attachment is an attachment handler used in emit args. All attachments will be sent as binary data in the transport layer. When using an attachment, make sure it is a pointer.
//
// For example:
//
// type Arg struct {
// Title string `json:"title"`
// File *Attachment `json:"file"`
// }
//
// f, _ := os.Open("./some_file")
// arg := Arg{
// Title: "some_file",
// File: &Attachment{
// Data: f,
// }
// }
//
// socket.Emit("send file", arg)
// socket.On("get file", func(so Socket, arg Arg) {
// b, _ := ioutil.ReadAll(arg.File.Data)
// })
type Attachment struct {
Data io.ReadWriter
num int
}
func encodeAttachments(v interface{}) []io.Reader {
index := 0
return encodeAttachmentValue(reflect.ValueOf(v), &index)
}
func encodeAttachmentValue(v reflect.Value, index *int) []io.Reader {
v = reflect.Indirect(v)
ret := []io.Reader{}
if !v.IsValid() {
return ret
}
switch v.Kind() {
case reflect.Struct:
if v.Type().Name() == "Attachment" {
a, ok := v.Addr().Interface().(*Attachment)
if !ok {
panic("can't convert")
}
a.num = *index
ret = append(ret, a.Data)
(*index)++
return ret
}
for i, n := 0, v.NumField(); i < n; i++ {
var r []io.Reader
r = encodeAttachmentValue(v.Field(i), index)
ret = append(ret, r...)
}
case reflect.Map:
if v.IsNil() {
return ret
}
for _, key := range v.MapKeys() {
var r []io.Reader
r = encodeAttachmentValue(v.MapIndex(key), index)
ret = append(ret, r...)
}
case reflect.Slice:
if v.IsNil() {
return ret
}
fallthrough
case reflect.Array:
for i, n := 0, v.Len(); i < n; i++ {
var r []io.Reader
r = encodeAttachmentValue(v.Index(i), index)
ret = append(ret, r...)
}
case reflect.Interface:
ret = encodeAttachmentValue(reflect.ValueOf(v.Interface()), index)
}
return ret
}
func decodeAttachments(v interface{}, binary [][]byte) error {
return decodeAttachmentValue(reflect.ValueOf(v), binary)
}
func decodeAttachmentValue(v reflect.Value, binary [][]byte) error {
v = reflect.Indirect(v)
if !v.IsValid() {
return fmt.Errorf("invalid value")
}
switch v.Kind() {
case reflect.Struct:
if v.Type().Name() == "Attachment" {
a, ok := v.Addr().Interface().(*Attachment)
if !ok {
panic("can't convert")
}
if a.num >= len(binary) || a.num < 0 {
return fmt.Errorf("out of range")
}
if a.Data == nil {
a.Data = bytes.NewBuffer(nil)
}
for b := binary[a.num]; len(b) > 0; {
n, err := a.Data.Write(b)
if err != nil {
return err
}
b = b[n:]
}
return nil
}
for i, n := 0, v.NumField(); i < n; i++ {
if err := decodeAttachmentValue(v.Field(i), binary); err != nil {
return err
}
}
case reflect.Map:
if v.IsNil() {
return nil
}
for _, key := range v.MapKeys() {
if err := decodeAttachmentValue(v.MapIndex(key), binary); err != nil {
return err
}
}
case reflect.Slice:
if v.IsNil() {
return nil
}
fallthrough
case reflect.Array:
for i, n := 0, v.Len(); i < n; i++ {
if err := decodeAttachmentValue(v.Index(i), binary); err != nil {
return err
}
}
case reflect.Interface:
if err := decodeAttachmentValue(reflect.ValueOf(v.Interface()), binary); err != nil {
return err
}
}
return nil
}
func (a Attachment) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("{\"_placeholder\":true,\"num\":%d}", a.num)), nil
}
func (a *Attachment) UnmarshalJSON(b []byte) error {
var v struct {
Num int `json:"num"`
}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
a.num = v.Num
return nil
}

82
vendor/github.com/googollee/go-socket.io/caller.go generated vendored Normal file
View File

@ -0,0 +1,82 @@
package socketio
import (
"errors"
"fmt"
"reflect"
)
type caller struct {
Func reflect.Value
Args []reflect.Type
NeedSocket bool
}
func newCaller(f interface{}) (*caller, error) {
fv := reflect.ValueOf(f)
if fv.Kind() != reflect.Func {
return nil, fmt.Errorf("f is not func")
}
ft := fv.Type()
if ft.NumIn() == 0 {
return &caller{
Func: fv,
}, nil
}
args := make([]reflect.Type, ft.NumIn())
for i, n := 0, ft.NumIn(); i < n; i++ {
args[i] = ft.In(i)
}
needSocket := false
if args[0].Name() == "Socket" {
args = args[1:]
needSocket = true
}
return &caller{
Func: fv,
Args: args,
NeedSocket: needSocket,
}, nil
}
func (c *caller) GetArgs() []interface{} {
ret := make([]interface{}, len(c.Args))
for i, argT := range c.Args {
if argT.Kind() == reflect.Ptr {
argT = argT.Elem()
}
v := reflect.New(argT)
ret[i] = v.Interface()
}
return ret
}
func (c *caller) Call(so Socket, args []interface{}) []reflect.Value {
var a []reflect.Value
diff := 0
if c.NeedSocket {
diff = 1
a = make([]reflect.Value, len(args)+1)
a[0] = reflect.ValueOf(so)
} else {
a = make([]reflect.Value, len(args))
}
if len(args) != len(c.Args) {
return []reflect.Value{reflect.ValueOf([]interface{}{}), reflect.ValueOf(errors.New("Arguments do not match"))}
}
for i, arg := range args {
v := reflect.ValueOf(arg)
if c.Args[i].Kind() != reflect.Ptr {
if v.IsValid() {
v = v.Elem()
} else {
v = reflect.Zero(c.Args[i])
}
}
a[i+diff] = v
}
return c.Func.Call(a)
}

213
vendor/github.com/googollee/go-socket.io/handler.go generated vendored Normal file
View File

@ -0,0 +1,213 @@
package socketio
import (
"fmt"
"reflect"
"sync"
)
type baseHandler struct {
events map[string]*caller
name string
broadcast BroadcastAdaptor
evMu sync.Mutex
}
func newBaseHandler(name string, broadcast BroadcastAdaptor) *baseHandler {
return &baseHandler{
events: make(map[string]*caller),
name: name,
broadcast: broadcast,
evMu: sync.Mutex{},
}
}
// On registers the function f to handle an event.
func (h *baseHandler) On(event string, f interface{}) error {
c, err := newCaller(f)
if err != nil {
return err
}
h.evMu.Lock()
h.events[event] = c
h.evMu.Unlock()
return nil
}
type socketHandler struct {
*baseHandler
acksmu sync.Mutex
acks map[int]*caller
socket *socket
rooms map[string]struct{}
}
func newSocketHandler(s *socket, base *baseHandler) *socketHandler {
events := make(map[string]*caller)
base.evMu.Lock()
for k, v := range base.events {
events[k] = v
}
base.evMu.Unlock()
return &socketHandler{
baseHandler: &baseHandler{
events: events,
broadcast: base.broadcast,
evMu: base.evMu,
},
acks: make(map[int]*caller),
socket: s,
rooms: make(map[string]struct{}),
}
}
func (h *socketHandler) Emit(event string, args ...interface{}) error {
var c *caller
if l := len(args); l > 0 {
fv := reflect.ValueOf(args[l-1])
if fv.Kind() == reflect.Func {
var err error
c, err = newCaller(args[l-1])
if err != nil {
return err
}
args = args[:l-1]
}
}
args = append([]interface{}{event}, args...)
if c != nil {
id, err := h.socket.sendId(args)
if err != nil {
return err
}
h.acksmu.Lock()
h.acks[id] = c
h.acksmu.Unlock()
return nil
}
return h.socket.send(args)
}
func (h *socketHandler) Rooms() []string {
ret := make([]string, len(h.rooms))
i := 0
for room := range h.rooms {
ret[i] = room
i++
}
return ret
}
func (h *socketHandler) Join(room string) error {
if err := h.baseHandler.broadcast.Join(h.broadcastName(room), h.socket); err != nil {
return err
}
h.rooms[room] = struct{}{}
return nil
}
func (h *socketHandler) Leave(room string) error {
if err := h.baseHandler.broadcast.Leave(h.broadcastName(room), h.socket); err != nil {
return err
}
delete(h.rooms, room)
return nil
}
func (h *socketHandler) LeaveAll() error {
for room := range h.rooms {
if err := h.baseHandler.broadcast.Leave(h.broadcastName(room), h.socket); err != nil {
return err
}
}
return nil
}
func (h *baseHandler) BroadcastTo(room, event string, args ...interface{}) error {
return h.broadcast.Send(nil, h.broadcastName(room), event, args...)
}
func (h *socketHandler) BroadcastTo(room, event string, args ...interface{}) error {
return h.baseHandler.broadcast.Send(h.socket, h.broadcastName(room), event, args...)
}
func (h *baseHandler) broadcastName(room string) string {
return fmt.Sprintf("%s:%s", h.name, room)
}
func (h *socketHandler) onPacket(decoder *decoder, packet *packet) ([]interface{}, error) {
var message string
switch packet.Type {
case _CONNECT:
message = "connection"
case _DISCONNECT:
message = "disconnection"
case _ERROR:
message = "error"
case _ACK:
fallthrough
case _BINARY_ACK:
return nil, h.onAck(packet.Id, decoder, packet)
default:
if decoder != nil {
message = decoder.Message()
}
}
h.evMu.Lock()
c, ok := h.events[message]
h.evMu.Unlock()
if !ok {
// If the message is not recognized by the server, the decoder.currentCloser
// needs to be closed otherwise the server will be stuck until the e
if decoder != nil {
decoder.Close()
}
return nil, nil
}
args := c.GetArgs()
olen := len(args)
if olen > 0 && decoder != nil {
packet.Data = &args
if err := decoder.DecodeData(packet); err != nil {
return nil, err
}
}
for i := len(args); i < olen; i++ {
args = append(args, nil)
}
retV := c.Call(h.socket, args)
if len(retV) == 0 {
return nil, nil
}
var err error
if last, ok := retV[len(retV)-1].Interface().(error); ok {
err = last
retV = retV[0 : len(retV)-1]
}
ret := make([]interface{}, len(retV))
for i, v := range retV {
ret[i] = v.Interface()
}
return ret, err
}
func (h *socketHandler) onAck(id int, decoder *decoder, packet *packet) error {
h.acksmu.Lock()
c, ok := h.acks[id]
if !ok {
h.acksmu.Unlock()
return nil
}
delete(h.acks, id)
h.acksmu.Unlock()
args := c.GetArgs()
packet.Data = &args
if err := decoder.DecodeData(packet); err != nil {
return err
}
c.Call(h.socket, args)
return nil
}

34
vendor/github.com/googollee/go-socket.io/ioutil.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
package socketio
import (
"io"
)
type writerHelper struct {
writer io.Writer
err error
}
func newWriterHelper(w io.Writer) *writerHelper {
return &writerHelper{
writer: w,
}
}
func (h *writerHelper) Write(p []byte) {
if h.err != nil {
return
}
for len(p) > 0 {
n, err := h.writer.Write(p)
if err != nil {
h.err = err
return
}
p = p[n:]
}
}
func (h *writerHelper) Error() error {
return h.err
}

6
vendor/github.com/googollee/go-socket.io/main.go generated vendored Normal file
View File

@ -0,0 +1,6 @@
/*
go-socket.io is a server implementation of socket.io in golang.
It is compatible with the official Node.js implementation.
*/
package socketio

View File

@ -0,0 +1,60 @@
package socketio
import (
"bufio"
)
type messageReader struct {
reader *bufio.Reader
message string
firstRead bool
}
func newMessageReader(bufr *bufio.Reader) (*messageReader, error) {
if _, err := bufr.ReadBytes('"'); err != nil {
return nil, err
}
msg, err := bufr.ReadBytes('"')
if err != nil {
return nil, err
}
for {
b, err := bufr.Peek(1)
if err != nil {
return nil, err
}
if b[0] == ',' {
bufr.ReadByte()
break
}
if b[0] != ' ' {
break
}
bufr.ReadByte()
}
return &messageReader{
reader: bufr,
message: string(msg[:len(msg)-1]),
firstRead: true,
}, nil
}
func (r *messageReader) Message() string {
return r.message
}
func (r *messageReader) Read(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
if r.firstRead {
r.firstRead = false
b[0] = '['
n, err := r.reader.Read(b[1:])
if err != nil {
return -1, err
}
return n + 1, err
}
return r.reader.Read(b)
}

47
vendor/github.com/googollee/go-socket.io/namespace.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package socketio
// Namespace is the name space of a socket.io handler.
type Namespace interface {
// Name returns the name of the namespace.
Name() string
// Of returns the namespace with given name.
Of(name string) Namespace
// On registers the function f to handle an event.
On(event string, f interface{}) error
}
type namespace struct {
*baseHandler
root map[string]Namespace
}
func newNamespace(broadcast BroadcastAdaptor) *namespace {
ret := &namespace{
baseHandler: newBaseHandler("", broadcast),
root: make(map[string]Namespace),
}
ret.root[ret.Name()] = ret
return ret
}
func (n *namespace) Name() string {
return n.baseHandler.name
}
func (n *namespace) Of(name string) Namespace {
if name == "/" {
name = ""
}
if ret, ok := n.root[name]; ok {
return ret
}
ret := &namespace{
baseHandler: newBaseHandler(name, n.baseHandler.broadcast),
root: n.root,
}
n.root[name] = ret
return ret
}

338
vendor/github.com/googollee/go-socket.io/parser.go generated vendored Normal file
View File

@ -0,0 +1,338 @@
package socketio
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"strconv"
"github.com/googollee/go-engine.io"
)
const Protocol = 4
type packetType int
const (
_CONNECT packetType = iota
_DISCONNECT
_EVENT
_ACK
_ERROR
_BINARY_EVENT
_BINARY_ACK
)
func (t packetType) String() string {
switch t {
case _CONNECT:
return "connect"
case _DISCONNECT:
return "disconnect"
case _EVENT:
return "event"
case _ACK:
return "ack"
case _ERROR:
return "error"
case _BINARY_EVENT:
return "binary_event"
case _BINARY_ACK:
return "binary_ack"
}
return fmt.Sprintf("unknown(%d)", t)
}
type frameReader interface {
NextReader() (engineio.MessageType, io.ReadCloser, error)
}
type frameWriter interface {
NextWriter(engineio.MessageType) (io.WriteCloser, error)
}
type packet struct {
Type packetType
NSP string
Id int
Data interface{}
attachNumber int
}
type encoder struct {
w frameWriter
err error
}
func newEncoder(w frameWriter) *encoder {
return &encoder{
w: w,
}
}
func (e *encoder) Encode(v packet) error {
attachments := encodeAttachments(v.Data)
v.attachNumber = len(attachments)
if v.attachNumber > 0 {
v.Type += _BINARY_EVENT - _EVENT
}
if err := e.encodePacket(v); err != nil {
return err
}
for _, a := range attachments {
if err := e.writeBinary(a); err != nil {
return err
}
}
return nil
}
func (e *encoder) encodePacket(v packet) error {
writer, err := e.w.NextWriter(engineio.MessageText)
if err != nil {
return err
}
defer writer.Close()
w := newTrimWriter(writer, "\n")
wh := newWriterHelper(w)
wh.Write([]byte{byte(v.Type) + '0'})
if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
wh.Write([]byte(fmt.Sprintf("%d-", v.attachNumber)))
}
needEnd := false
if v.NSP != "" {
wh.Write([]byte(v.NSP))
needEnd = true
}
if v.Id >= 0 {
f := "%d"
if needEnd {
f = ",%d"
needEnd = false
}
wh.Write([]byte(fmt.Sprintf(f, v.Id)))
}
if v.Data != nil {
if needEnd {
wh.Write([]byte{','})
needEnd = false
}
if wh.Error() != nil {
return wh.Error()
}
encoder := json.NewEncoder(w)
return encoder.Encode(v.Data)
}
return wh.Error()
}
func (e *encoder) writeBinary(r io.Reader) error {
writer, err := e.w.NextWriter(engineio.MessageBinary)
if err != nil {
return err
}
defer writer.Close()
if _, err := io.Copy(writer, r); err != nil {
return err
}
return nil
}
type decoder struct {
reader frameReader
message string
current io.Reader
currentCloser io.Closer
}
func newDecoder(r frameReader) *decoder {
return &decoder{
reader: r,
}
}
func (d *decoder) Close() {
if d != nil && d.currentCloser != nil {
d.currentCloser.Close()
d.current = nil
d.currentCloser = nil
}
}
func (d *decoder) Decode(v *packet) error {
ty, r, err := d.reader.NextReader()
if err != nil {
return err
}
if d.current != nil {
d.Close()
}
defer func() {
if d.current == nil {
r.Close()
}
}()
if ty != engineio.MessageText {
return fmt.Errorf("need text package")
}
reader := bufio.NewReader(r)
v.Id = -1
t, err := reader.ReadByte()
if err != nil {
return err
}
v.Type = packetType(t - '0')
if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
num, err := reader.ReadBytes('-')
if err != nil {
return err
}
numLen := len(num)
if numLen == 0 {
return fmt.Errorf("invalid packet")
}
n, err := strconv.ParseInt(string(num[:numLen-1]), 10, 64)
if err != nil {
return fmt.Errorf("invalid packet")
}
v.attachNumber = int(n)
}
next, err := reader.Peek(1)
if err == io.EOF {
return nil
}
if err != nil {
return err
}
if len(next) == 0 {
return fmt.Errorf("invalid packet")
}
if next[0] == '/' {
path, err := reader.ReadBytes(',')
if err != nil && err != io.EOF {
return err
}
pathLen := len(path)
if pathLen == 0 {
return fmt.Errorf("invalid packet")
}
if err == nil {
path = path[:pathLen-1]
}
v.NSP = string(path)
if err == io.EOF {
return nil
}
}
id := bytes.NewBuffer(nil)
finish := false
for {
next, err := reader.Peek(1)
if err == io.EOF {
finish = true
break
}
if err != nil {
return err
}
if '0' <= next[0] && next[0] <= '9' {
if err := id.WriteByte(next[0]); err != nil {
return err
}
} else {
break
}
reader.ReadByte()
}
if id.Len() > 0 {
id, err := strconv.ParseInt(id.String(), 10, 64)
if err != nil {
return err
}
v.Id = int(id)
}
if finish {
return nil
}
switch v.Type {
case _EVENT:
fallthrough
case _BINARY_EVENT:
msgReader, err := newMessageReader(reader)
if err != nil {
return err
}
d.message = msgReader.Message()
d.current = msgReader
d.currentCloser = r
case _ACK:
fallthrough
case _BINARY_ACK:
d.current = reader
d.currentCloser = r
}
return nil
}
func (d *decoder) Message() string {
return d.message
}
func (d *decoder) DecodeData(v *packet) error {
if d.current == nil {
return nil
}
defer func() {
d.Close()
}()
decoder := json.NewDecoder(d.current)
if err := decoder.Decode(v.Data); err != nil {
return err
}
if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
binary, err := d.decodeBinary(v.attachNumber)
if err != nil {
return err
}
if err := decodeAttachments(v.Data, binary); err != nil {
return err
}
v.Type -= _BINARY_EVENT - _EVENT
}
return nil
}
func (d *decoder) decodeBinary(num int) ([][]byte, error) {
ret := make([][]byte, num)
for i := 0; i < num; i++ {
d.currentCloser.Close()
t, r, err := d.reader.NextReader()
if err != nil {
return nil, err
}
d.currentCloser = r
if t == engineio.MessageText {
return nil, fmt.Errorf("need binary")
}
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
ret[i] = b
}
return ret, nil
}

106
vendor/github.com/googollee/go-socket.io/server.go generated vendored Normal file
View File

@ -0,0 +1,106 @@
package socketio
import (
"github.com/googollee/go-engine.io"
"net/http"
"time"
)
// Server is the server of socket.io.
type Server struct {
*namespace
broadcast BroadcastAdaptor
eio *engineio.Server
}
// NewServer returns the server supported given transports. If transports is nil, the server will use ["polling", "websocket"] as default.
func NewServer(transportNames []string) (*Server, error) {
eio, err := engineio.NewServer(transportNames)
if err != nil {
return nil, err
}
ret := &Server{
namespace: newNamespace(newBroadcastDefault()),
eio: eio,
}
go ret.loop()
return ret, nil
}
// SetPingTimeout sets the timeout of a connection ping. When it times out, the server will close the connection with the client. Default is 60s.
func (s *Server) SetPingTimeout(t time.Duration) {
s.eio.SetPingTimeout(t)
}
// SetPingInterval sets the interval of pings. Default is 25s.
func (s *Server) SetPingInterval(t time.Duration) {
s.eio.SetPingInterval(t)
}
// SetMaxConnection sets the maximum number of connections with clients. Default is 1000.
func (s *Server) SetMaxConnection(n int) {
s.eio.SetMaxConnection(n)
}
// GetMaxConnection returns the current max connection
func (s *Server) GetMaxConnection() int {
return s.eio.GetMaxConnection()
}
// Count returns the current number of connected clients in session
func (s *Server) Count() int {
return s.eio.Count()
}
// SetAllowRequest sets the middleware function when a connection is established. If a non-nil value is returned, the connection won't be established. Default will allow all connections.
func (s *Server) SetAllowRequest(f func(*http.Request) error) {
s.eio.SetAllowRequest(f)
}
// SetAllowUpgrades sets whether server allows transport upgrades. Default is true.
func (s *Server) SetAllowUpgrades(allow bool) {
s.eio.SetAllowUpgrades(allow)
}
// SetCookie sets the name of the cookie used by engine.io. Default is "io".
func (s *Server) SetCookie(prefix string) {
s.eio.SetCookie(prefix)
}
// SetNewId sets the callback func to generate new connection id. By default, id is generated from remote address + current time stamp
func (s *Server) SetNewId(f func(*http.Request) string) {
s.eio.SetNewId(f)
}
// SetSessionsManager sets the sessions as server's session manager. Default sessions is a single process manager. You can customize it as a load balancer.
func (s *Server) SetSessionManager(sessions engineio.Sessions) {
s.eio.SetSessionManager(sessions)
}
// SetAdaptor sets the adaptor of broadcast. Default is an in-process broadcast implementation.
func (s *Server) SetAdaptor(adaptor BroadcastAdaptor) {
s.namespace = newNamespace(adaptor)
}
// ServeHTTP handles http requests.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.eio.ServeHTTP(w, r)
}
// BroadcastTo is a server level broadcast function.
func (s *Server) BroadcastTo(room, message string, args ...interface{}) {
s.namespace.BroadcastTo(room, message, args...)
}
func (s *Server) loop() {
for {
conn, err := s.eio.Accept()
if err != nil {
return
}
s := newSocket(conn, s.baseHandler)
go func(s *socket) {
s.loop()
}(s)
}
}

174
vendor/github.com/googollee/go-socket.io/socket.go generated vendored Normal file
View File

@ -0,0 +1,174 @@
package socketio
import (
"net/http"
"sync"
"github.com/googollee/go-engine.io"
)
// Socket is the socket object of socket.io.
type Socket interface {
// Id returns the session id of socket.
Id() string
// Rooms returns the rooms name joined now.
Rooms() []string
// Request returns the first http request when established connection.
Request() *http.Request
// On registers the function f to handle an event.
On(event string, f interface{}) error
// Emit emits an event with given args.
Emit(event string, args ...interface{}) error
// Join joins the room.
Join(room string) error
// Leave leaves the room.
Leave(room string) error
// Disconnect disconnect the socket.
Disconnect()
// BroadcastTo broadcasts an event to the room with given args.
BroadcastTo(room, event string, args ...interface{}) error
}
type socket struct {
*socketHandler
conn engineio.Conn
namespace string
id int
mu sync.Mutex
}
func newSocket(conn engineio.Conn, base *baseHandler) *socket {
ret := &socket{
conn: conn,
}
ret.socketHandler = newSocketHandler(ret, base)
return ret
}
func (s *socket) Id() string {
return s.conn.Id()
}
func (s *socket) Request() *http.Request {
return s.conn.Request()
}
func (s *socket) Emit(event string, args ...interface{}) error {
if err := s.socketHandler.Emit(event, args...); err != nil {
return err
}
if event == "disconnect" {
s.conn.Close()
}
return nil
}
func (s *socket) Disconnect() {
s.conn.Close()
}
func (s *socket) send(args []interface{}) error {
packet := packet{
Type: _EVENT,
Id: -1,
NSP: s.namespace,
Data: args,
}
encoder := newEncoder(s.conn)
return encoder.Encode(packet)
}
func (s *socket) sendConnect() error {
packet := packet{
Type: _CONNECT,
Id: -1,
NSP: s.namespace,
}
encoder := newEncoder(s.conn)
return encoder.Encode(packet)
}
func (s *socket) sendId(args []interface{}) (int, error) {
s.mu.Lock()
packet := packet{
Type: _EVENT,
Id: s.id,
NSP: s.namespace,
Data: args,
}
s.id++
if s.id < 0 {
s.id = 0
}
s.mu.Unlock()
encoder := newEncoder(s.conn)
err := encoder.Encode(packet)
if err != nil {
return -1, nil
}
return packet.Id, nil
}
func (s *socket) loop() error {
defer func() {
s.LeaveAll()
p := packet{
Type: _DISCONNECT,
Id: -1,
}
s.socketHandler.onPacket(nil, &p)
}()
p := packet{
Type: _CONNECT,
Id: -1,
}
encoder := newEncoder(s.conn)
if err := encoder.Encode(p); err != nil {
return err
}
s.socketHandler.onPacket(nil, &p)
for {
decoder := newDecoder(s.conn)
var p packet
if err := decoder.Decode(&p); err != nil {
return err
}
ret, err := s.socketHandler.onPacket(decoder, &p)
if err != nil {
return err
}
switch p.Type {
case _CONNECT:
s.namespace = p.NSP
s.sendConnect()
case _BINARY_EVENT:
fallthrough
case _EVENT:
if p.Id >= 0 {
p := packet{
Type: _ACK,
Id: p.Id,
NSP: s.namespace,
Data: ret,
}
encoder := newEncoder(s.conn)
if err := encoder.Encode(p); err != nil {
return err
}
}
case _DISCONNECT:
return nil
}
}
}

View File

@ -0,0 +1,45 @@
package socketio
import (
"bytes"
"io"
)
type trimWriter struct {
trimChars string
trimBuf []byte
output io.Writer
}
func newTrimWriter(w io.Writer, trimChars string) *trimWriter {
return &trimWriter{
trimChars: trimChars,
output: w,
}
}
func (w *trimWriter) Write(p []byte) (int, error) {
out := bytes.TrimRight(p, w.trimChars)
buf := p[len(out):]
var written int
if (len(out) > 0) && (w.trimBuf != nil) {
var err error
if written, err = w.output.Write(w.trimBuf); err != nil {
return 0, err
}
w.trimBuf = nil
}
if w.trimBuf != nil {
w.trimBuf = append(w.trimBuf, buf...)
} else {
w.trimBuf = buf
}
if len(p) == 0 {
return written, nil
}
ret, err := w.output.Write(out)
if err != nil {
return 0, err
}
return written + ret, nil
}

25
vendor/github.com/gorilla/websocket/.gitignore generated vendored Normal file
View File

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

19
vendor/github.com/gorilla/websocket/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,19 @@
language: go
sudo: false
matrix:
include:
- go: 1.4
- go: 1.5
- go: 1.6
- go: 1.7
- go: 1.8
- go: tip
allow_failures:
- go: tip
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go vet $(go list ./... | grep -v /vendor/)
- go test -v -race ./...

8
vendor/github.com/gorilla/websocket/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,8 @@
# This is the official list of Gorilla WebSocket authors for copyright
# purposes.
#
# Please keep the list sorted.
Gary Burd <gary@beagledreams.com>
Joachim Bauch <mail@joachim-bauch.de>

22
vendor/github.com/gorilla/websocket/LICENSE generated vendored Normal file
View File

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

64
vendor/github.com/gorilla/websocket/README.md generated vendored Normal file
View File

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

392
vendor/github.com/gorilla/websocket/client.go generated vendored Normal file
View File

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

16
vendor/github.com/gorilla/websocket/client_clone.go generated vendored Normal file
View File

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

View File

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

148
vendor/github.com/gorilla/websocket/compression.go generated vendored Normal file
View File

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

1149
vendor/github.com/gorilla/websocket/conn.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

18
vendor/github.com/gorilla/websocket/conn_read.go generated vendored Normal file
View File

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

View File

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

180
vendor/github.com/gorilla/websocket/doc.go generated vendored Normal file
View File

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

55
vendor/github.com/gorilla/websocket/json.go generated vendored Normal file
View File

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

55
vendor/github.com/gorilla/websocket/mask.go generated vendored Normal file
View File

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

15
vendor/github.com/gorilla/websocket/mask_safe.go generated vendored Normal file
View File

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

103
vendor/github.com/gorilla/websocket/prepared.go generated vendored Normal file
View File

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

291
vendor/github.com/gorilla/websocket/server.go generated vendored Normal file
View File

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

214
vendor/github.com/gorilla/websocket/util.go generated vendored Normal file
View File

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

15
vendor/modules.txt vendored Normal file
View File

@ -0,0 +1,15 @@
# code.ivysaur.me/libnmdc v0.16.0
code.ivysaur.me/libnmdc
# github.com/cxmcc/tiger v0.0.0-20170524142333-bde35e2713d7
github.com/cxmcc/tiger
# github.com/googollee/go-engine.io v0.0.0-20170224222511-80ae0e43aca1
github.com/googollee/go-engine.io
github.com/googollee/go-engine.io/message
github.com/googollee/go-engine.io/parser
github.com/googollee/go-engine.io/polling
github.com/googollee/go-engine.io/transport
github.com/googollee/go-engine.io/websocket
# github.com/googollee/go-socket.io v0.0.0-20170525141029-5447e71f36d3
github.com/googollee/go-socket.io
# github.com/gorilla/websocket v1.2.0
github.com/gorilla/websocket