Initial commit
This commit is contained in:
commit
d7a81743c6
25
.devcontainer/devcontainer.json
Normal file
25
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,25 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.241.1/containers/go
|
||||
{
|
||||
"name": "Go",
|
||||
"image": "docker.io/library/golang:1.19-bullseye",
|
||||
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"go.toolsManagement.checkForUpdates": "local",
|
||||
"go.useLanguageServer": true,
|
||||
"go.gopath": "/go"
|
||||
},
|
||||
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"golang.Go"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
autosnooze
|
||||
autosnooze-arm
|
||||
autosnooze.exe
|
||||
config.yaml
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Tony Grosinger
|
||||
|
||||
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.
|
31
Makefile
Normal file
31
Makefile
Normal file
@ -0,0 +1,31 @@
|
||||
# Build the application binary and static files.
|
||||
# This should be called from within the dev container.
|
||||
.PHONY: build
|
||||
build:
|
||||
rm -f autosnooze
|
||||
go build \
|
||||
-mod=vendor \
|
||||
-o autosnooze \
|
||||
cmd/autosnooze/main.go
|
||||
|
||||
.PHONY: build-windows
|
||||
build-windows:
|
||||
rm -f autosnooze.exe
|
||||
GOOS=windows go build \
|
||||
-mod=vendor \
|
||||
-o autosnooze.exe \
|
||||
cmd/autosnooze/main.go
|
||||
|
||||
.PHONY: build-arm
|
||||
build-arm:
|
||||
rm -f autosnooze-arm
|
||||
GOARCH=arm64 go build \
|
||||
-mod=vendor \
|
||||
-o autosnooze-arm \
|
||||
cmd/autosnooze/main.go
|
||||
|
||||
.PHONY: run
|
||||
run:
|
||||
go run \
|
||||
-mod=vendor \
|
||||
cmd/autosnooze/main.go /workspaces/autosnooze/config.yaml
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# AutoSnooze
|
||||
|
||||
Replicate the email snooze functionality common in webmail clients using folders
|
||||
over IMAP.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Build and run the application to output an example config file.
|
||||
2. Save and adjust the config file as necessary.
|
||||
3. Setup the application to run at regular intervals, for example every night at 23:00.
|
||||
4. Create relative folders, for example "AutoSnooze/Tomorrow" and "AutoSnooze/Next Weekend"
|
||||
5. Place emails in folders as desired.
|
||||
|
32
go.mod
Normal file
32
go.mod
Normal file
@ -0,0 +1,32 @@
|
||||
module git.sr.ht/~tgrosinger/autosnooze
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/emersion/go-imap v1.2.1
|
||||
github.com/spf13/viper v1.14.0
|
||||
github.com/tj/go-naturaldate v1.3.0
|
||||
go.uber.org/zap v1.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/spf13/afero v1.9.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
499
go.sum
Normal file
499
go.sum
Normal file
@ -0,0 +1,499 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
||||
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
|
||||
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
|
||||
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160 h1:NSWpaDaurcAJY7PkL8Xt0PhZE7qpvbZl5ljd8r6U0bI=
|
||||
github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
|
||||
github.com/tj/go-naturaldate v1.3.0 h1:OgJIPkR/Jk4bFMBLbxZ8w+QUxwjqSvzd9x+yXocY4RI=
|
||||
github.com/tj/go-naturaldate v1.3.0/go.mod h1:rpUbjivDKiS1BlfMGc2qUKNZ/yxgthOfmytQs8d8hKk=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
17
pkg/config/config.go
Normal file
17
pkg/config/config.go
Normal file
@ -0,0 +1,17 @@
|
||||
package config
|
||||
|
||||
type Config struct {
|
||||
LogLevel string
|
||||
|
||||
IMAP struct {
|
||||
ServerName string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
Mailbox struct {
|
||||
Prefix string
|
||||
DateFormat string
|
||||
}
|
||||
}
|
181
pkg/mailbox/mailbox.go
Normal file
181
pkg/mailbox/mailbox.go
Normal file
@ -0,0 +1,181 @@
|
||||
package mailbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
imapClient "github.com/emersion/go-imap/client"
|
||||
"github.com/tj/go-naturaldate"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"git.sr.ht/~tgrosinger/autosnooze/pkg/config"
|
||||
)
|
||||
|
||||
type AccountProcessor struct {
|
||||
Client *imapClient.Client
|
||||
Logger *zap.Logger
|
||||
Config config.Config
|
||||
}
|
||||
|
||||
type Mailbox struct {
|
||||
// Prefix is the portion of the name which comes before the portion that
|
||||
// should be parsed as a date or relative date. e.g. "AutoSnooze/"
|
||||
Prefix string
|
||||
|
||||
// FullName is the name as returned from the IMAP server.
|
||||
FullName string
|
||||
}
|
||||
|
||||
func (m *Mailbox) StrippedName() string {
|
||||
return strings.TrimPrefix(m.FullName, m.Prefix)
|
||||
}
|
||||
|
||||
func (m *Mailbox) Date(format string) (time.Time, error) {
|
||||
return time.Parse(format, m.StrippedName())
|
||||
}
|
||||
|
||||
func (p *AccountProcessor) Process() {
|
||||
// List mailboxes
|
||||
mailboxes := make(chan *imap.MailboxInfo, 10)
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- p.Client.List("", "*", mailboxes)
|
||||
}()
|
||||
|
||||
// The IMAP library does not seem to be thread-safe, so we will collect the
|
||||
// mailboxes and process them sequentially.
|
||||
toProcess := make([]*Mailbox, 0, 10)
|
||||
for m := range mailboxes {
|
||||
if !strings.HasPrefix(m.Name, p.Config.Mailbox.Prefix) {
|
||||
p.Logger.Debug("skipping mailbox", zap.String("mailbox", m.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
toProcess = append(toProcess, &Mailbox{
|
||||
Prefix: p.Config.Mailbox.Prefix,
|
||||
FullName: m.Name,
|
||||
})
|
||||
}
|
||||
|
||||
if err := <-done; err != nil {
|
||||
p.Logger.Error("failed while processing mailboxes", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, mailbox := range toProcess {
|
||||
// Separate mailboxes into absolute and relative
|
||||
if _, err := mailbox.Date(p.Config.Mailbox.DateFormat); err == nil {
|
||||
p.processAbsoluteMailbox(mailbox)
|
||||
} else {
|
||||
p.processRelativeMailbox(mailbox)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processAbsoluteMailbox checks if the date represented by the mailbox name is
|
||||
// today and if so, moves the contained mail into the inbox.
|
||||
func (p *AccountProcessor) processAbsoluteMailbox(mailbox *Mailbox) {
|
||||
logger := p.Logger.With(zap.String("mailbox", mailbox.FullName))
|
||||
logger.Debug("processing absolute mailbox")
|
||||
|
||||
date, err := mailbox.Date(p.Config.Mailbox.DateFormat)
|
||||
if err != nil {
|
||||
logger.Warn("failed to parse absolute mailbox name as date", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if !date.Before(time.Now()) {
|
||||
logger.Debug("skipping absolute mailbox which is not for today")
|
||||
return
|
||||
}
|
||||
|
||||
mBox, err := p.Client.Select(mailbox.FullName, false)
|
||||
if err != nil {
|
||||
logger.Warn("failed to select mailbox to check for messages", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if mBox.Messages == 0 {
|
||||
logger.Debug("mailbox contains no messages")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("moving messages from mailbox into inbox")
|
||||
seqset := new(imap.SeqSet)
|
||||
seqset.AddRange(1, mBox.Messages) // All messages
|
||||
err = p.Client.Move(seqset, "INBOX")
|
||||
if err != nil {
|
||||
logger.Warn("unable to move messages to target mailbox", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
p.deleteMailboxIfEmpty(mailbox)
|
||||
}
|
||||
|
||||
// processRelativeMailbox checks if there is any mail in this folder, if so it
|
||||
// finds or creates the cooresponding absolute folder and moves the mail to that
|
||||
// folder.
|
||||
// e.g. AutoSnooze/tomorrow => AutoSnooze/2022-12-31
|
||||
func (p *AccountProcessor) processRelativeMailbox(mailbox *Mailbox) {
|
||||
logger := p.Logger.With(zap.String("mailbox", mailbox.FullName))
|
||||
logger.Debug("processing relative mailbox")
|
||||
|
||||
mBox, err := p.Client.Select(mailbox.FullName, false)
|
||||
if err != nil {
|
||||
logger.Warn("failed to select mailbox to check for messages", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if mBox.Messages == 0 {
|
||||
logger.Debug("mailbox contains no messages")
|
||||
return
|
||||
}
|
||||
|
||||
d, err := naturaldate.Parse(mailbox.StrippedName(), time.Now(),
|
||||
naturaldate.WithDirection(naturaldate.Future))
|
||||
if err != nil {
|
||||
logger.Debug("unable to parse mailbox as relative time", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
targetMailboxName := fmt.Sprintf(mailbox.Prefix + d.Format(p.Config.Mailbox.DateFormat))
|
||||
logger = logger.With(zap.String("target_mailbox", targetMailboxName))
|
||||
|
||||
err = p.Client.Create(targetMailboxName)
|
||||
// Create does not actually return this sential error, just a matching message.
|
||||
if err != nil && err.Error() != backend.ErrMailboxAlreadyExists.Error() {
|
||||
logger.Warn("unable to create target mailbox", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("moving messages from mailbox into target")
|
||||
seqset := new(imap.SeqSet)
|
||||
seqset.AddRange(1, mBox.Messages) // All messages
|
||||
err = p.Client.Move(seqset, targetMailboxName)
|
||||
if err != nil {
|
||||
logger.Warn("unable to move messages to target mailbox", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *AccountProcessor) deleteMailboxIfEmpty(mailbox *Mailbox) {
|
||||
logger := p.Logger.With(zap.String("mailbox", mailbox.FullName))
|
||||
|
||||
mBox, err := p.Client.Select(mailbox.FullName, false)
|
||||
if err != nil {
|
||||
logger.Warn("failed to select mailbox to check for messages", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if mBox.Messages != 0 {
|
||||
logger.Debug("mailbox contains messages, not deleting")
|
||||
return
|
||||
}
|
||||
|
||||
err = p.Client.Delete(mailbox.FullName)
|
||||
if err != nil {
|
||||
logger.Warn("failed to delete mailbox", zap.Error(err))
|
||||
}
|
||||
}
|
17
vendor/github.com/emersion/go-imap/.build.yml
generated
vendored
Normal file
17
vendor/github.com/emersion/go-imap/.build.yml
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
image: alpine/edge
|
||||
packages:
|
||||
- go
|
||||
sources:
|
||||
- https://github.com/emersion/go-imap
|
||||
artifacts:
|
||||
- coverage.html
|
||||
tasks:
|
||||
- build: |
|
||||
cd go-imap
|
||||
go build -race -v ./...
|
||||
- test: |
|
||||
cd go-imap
|
||||
go test -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
- coverage: |
|
||||
cd go-imap
|
||||
go tool cover -html=coverage.txt -o ~/coverage.html
|
28
vendor/github.com/emersion/go-imap/.gitignore
generated
vendored
Normal file
28
vendor/github.com/emersion/go-imap/.gitignore
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
/client.go
|
||||
/server.go
|
||||
coverage.txt
|
23
vendor/github.com/emersion/go-imap/LICENSE
generated
vendored
Normal file
23
vendor/github.com/emersion/go-imap/LICENSE
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 The Go-IMAP Authors
|
||||
Copyright (c) 2016 emersion
|
||||
Copyright (c) 2016 Proton Technologies AG
|
||||
|
||||
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.
|
178
vendor/github.com/emersion/go-imap/README.md
generated
vendored
Normal file
178
vendor/github.com/emersion/go-imap/README.md
generated
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
# go-imap
|
||||
|
||||
[![godocs.io](https://godocs.io/github.com/emersion/go-imap?status.svg)](https://godocs.io/github.com/emersion/go-imap)
|
||||
[![builds.sr.ht status](https://builds.sr.ht/~emersion/go-imap/commits/master.svg)](https://builds.sr.ht/~emersion/go-imap/commits/master?)
|
||||
|
||||
An [IMAP4rev1](https://tools.ietf.org/html/rfc3501) library written in Go. It
|
||||
can be used to build a client and/or a server.
|
||||
|
||||
## Usage
|
||||
|
||||
### Client [![godocs.io](https://godocs.io/github.com/emersion/go-imap/client?status.svg)](https://godocs.io/github.com/emersion/go-imap/client)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/emersion/go-imap/client"
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Println("Connecting to server...")
|
||||
|
||||
// Connect to server
|
||||
c, err := client.DialTLS("mail.example.org:993", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Connected")
|
||||
|
||||
// Don't forget to logout
|
||||
defer c.Logout()
|
||||
|
||||
// Login
|
||||
if err := c.Login("username", "password"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Logged in")
|
||||
|
||||
// List mailboxes
|
||||
mailboxes := make(chan *imap.MailboxInfo, 10)
|
||||
done := make(chan error, 1)
|
||||
go func () {
|
||||
done <- c.List("", "*", mailboxes)
|
||||
}()
|
||||
|
||||
log.Println("Mailboxes:")
|
||||
for m := range mailboxes {
|
||||
log.Println("* " + m.Name)
|
||||
}
|
||||
|
||||
if err := <-done; err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Select INBOX
|
||||
mbox, err := c.Select("INBOX", false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Flags for INBOX:", mbox.Flags)
|
||||
|
||||
// Get the last 4 messages
|
||||
from := uint32(1)
|
||||
to := mbox.Messages
|
||||
if mbox.Messages > 3 {
|
||||
// We're using unsigned integers here, only subtract if the result is > 0
|
||||
from = mbox.Messages - 3
|
||||
}
|
||||
seqset := new(imap.SeqSet)
|
||||
seqset.AddRange(from, to)
|
||||
|
||||
messages := make(chan *imap.Message, 10)
|
||||
done = make(chan error, 1)
|
||||
go func() {
|
||||
done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope}, messages)
|
||||
}()
|
||||
|
||||
log.Println("Last 4 messages:")
|
||||
for msg := range messages {
|
||||
log.Println("* " + msg.Envelope.Subject)
|
||||
}
|
||||
|
||||
if err := <-done; err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Done!")
|
||||
}
|
||||
```
|
||||
|
||||
### Server [![godocs.io](https://godocs.io/github.com/emersion/go-imap/server?status.svg)](https://godocs.io/github.com/emersion/go-imap/server)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/emersion/go-imap/server"
|
||||
"github.com/emersion/go-imap/backend/memory"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a memory backend
|
||||
be := memory.New()
|
||||
|
||||
// Create a new server
|
||||
s := server.New(be)
|
||||
s.Addr = ":1143"
|
||||
// Since we will use this server for testing only, we can allow plain text
|
||||
// authentication over unencrypted connections
|
||||
s.AllowInsecureAuth = true
|
||||
|
||||
log.Println("Starting IMAP server at localhost:1143")
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can now use `telnet localhost 1143` to manually connect to the server.
|
||||
|
||||
## Extensions
|
||||
|
||||
Support for several IMAP extensions is included in go-imap itself. This
|
||||
includes:
|
||||
|
||||
* [APPENDLIMIT](https://tools.ietf.org/html/rfc7889)
|
||||
* [CHILDREN](https://tools.ietf.org/html/rfc3348)
|
||||
* [ENABLE](https://tools.ietf.org/html/rfc5161)
|
||||
* [IDLE](https://tools.ietf.org/html/rfc2177)
|
||||
* [IMPORTANT](https://tools.ietf.org/html/rfc8457)
|
||||
* [LITERAL+](https://tools.ietf.org/html/rfc7888)
|
||||
* [MOVE](https://tools.ietf.org/html/rfc6851)
|
||||
* [SASL-IR](https://tools.ietf.org/html/rfc4959)
|
||||
* [SPECIAL-USE](https://tools.ietf.org/html/rfc6154)
|
||||
* [UNSELECT](https://tools.ietf.org/html/rfc3691)
|
||||
|
||||
Support for other extensions is provided via separate packages. See below.
|
||||
|
||||
## Extending go-imap
|
||||
|
||||
### Extensions
|
||||
|
||||
Commands defined in IMAP extensions are available in other packages. See [the
|
||||
wiki](https://github.com/emersion/go-imap/wiki/Using-extensions#using-client-extensions)
|
||||
to learn how to use them.
|
||||
|
||||
* [COMPRESS](https://github.com/emersion/go-imap-compress)
|
||||
* [ID](https://github.com/ProtonMail/go-imap-id)
|
||||
* [METADATA](https://github.com/emersion/go-imap-metadata)
|
||||
* [NAMESPACE](https://github.com/foxcpp/go-imap-namespace)
|
||||
* [QUOTA](https://github.com/emersion/go-imap-quota)
|
||||
* [SORT and THREAD](https://github.com/emersion/go-imap-sortthread)
|
||||
* [UIDPLUS](https://github.com/emersion/go-imap-uidplus)
|
||||
|
||||
### Server backends
|
||||
|
||||
* [Memory](https://github.com/emersion/go-imap/tree/master/backend/memory) (for testing)
|
||||
* [Multi](https://github.com/emersion/go-imap-multi)
|
||||
* [PGP](https://github.com/emersion/go-imap-pgp)
|
||||
* [Proxy](https://github.com/emersion/go-imap-proxy)
|
||||
* [Notmuch](https://github.com/stbenjam/go-imap-notmuch) - Experimental gateway for [Notmuch](https://notmuchmail.org/)
|
||||
|
||||
### Related projects
|
||||
|
||||
* [go-message](https://github.com/emersion/go-message) - parsing and formatting MIME and mail messages
|
||||
* [go-msgauth](https://github.com/emersion/go-msgauth) - handle DKIM, DMARC and Authentication-Results
|
||||
* [go-pgpmail](https://github.com/emersion/go-pgpmail) - decrypting and encrypting mails with OpenPGP
|
||||
* [go-sasl](https://github.com/emersion/go-sasl) - sending and receiving SASL authentications
|
||||
* [go-smtp](https://github.com/emersion/go-smtp) - building SMTP clients and servers
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
29
vendor/github.com/emersion/go-imap/backend/appendlimit.go
generated
vendored
Normal file
29
vendor/github.com/emersion/go-imap/backend/appendlimit.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// An error that should be returned by User.CreateMessage when the message size
|
||||
// is too big.
|
||||
var ErrTooBig = errors.New("Message size exceeding limit")
|
||||
|
||||
// A backend that supports retrieving per-user message size limits.
|
||||
type AppendLimitBackend interface {
|
||||
Backend
|
||||
|
||||
// Get the fixed maximum message size in octets that the backend will accept
|
||||
// when creating a new message. If there is no limit, return nil.
|
||||
CreateMessageLimit() *uint32
|
||||
}
|
||||
|
||||
// A user that supports retrieving per-user message size limits.
|
||||
type AppendLimitUser interface {
|
||||
User
|
||||
|
||||
// Get the fixed maximum message size in octets that the backend will accept
|
||||
// when creating a new message. If there is no limit, return nil.
|
||||
//
|
||||
// This overrides the global backend limit.
|
||||
CreateMessageLimit() *uint32
|
||||
}
|
20
vendor/github.com/emersion/go-imap/backend/backend.go
generated
vendored
Normal file
20
vendor/github.com/emersion/go-imap/backend/backend.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// Package backend defines an IMAP server backend interface.
|
||||
package backend
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// ErrInvalidCredentials is returned by Backend.Login when a username or a
|
||||
// password is incorrect.
|
||||
var ErrInvalidCredentials = errors.New("Invalid credentials")
|
||||
|
||||
// Backend is an IMAP server backend. A backend operation always deals with
|
||||
// users.
|
||||
type Backend interface {
|
||||
// Login authenticates a user. If the username or the password is incorrect,
|
||||
// it returns ErrInvalidCredentials.
|
||||
Login(connInfo *imap.ConnInfo, username, password string) (User, error)
|
||||
}
|
78
vendor/github.com/emersion/go-imap/backend/mailbox.go
generated
vendored
Normal file
78
vendor/github.com/emersion/go-imap/backend/mailbox.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Mailbox represents a mailbox belonging to a user in the mail storage system.
|
||||
// A mailbox operation always deals with messages.
|
||||
type Mailbox interface {
|
||||
// Name returns this mailbox name.
|
||||
Name() string
|
||||
|
||||
// Info returns this mailbox info.
|
||||
Info() (*imap.MailboxInfo, error)
|
||||
|
||||
// Status returns this mailbox status. The fields Name, Flags, PermanentFlags
|
||||
// and UnseenSeqNum in the returned MailboxStatus must be always populated.
|
||||
// This function does not affect the state of any messages in the mailbox. See
|
||||
// RFC 3501 section 6.3.10 for a list of items that can be requested.
|
||||
Status(items []imap.StatusItem) (*imap.MailboxStatus, error)
|
||||
|
||||
// SetSubscribed adds or removes the mailbox to the server's set of "active"
|
||||
// or "subscribed" mailboxes.
|
||||
SetSubscribed(subscribed bool) error
|
||||
|
||||
// Check requests a checkpoint of the currently selected mailbox. A checkpoint
|
||||
// refers to any implementation-dependent housekeeping associated with the
|
||||
// mailbox (e.g., resolving the server's in-memory state of the mailbox with
|
||||
// the state on its disk). A checkpoint MAY take a non-instantaneous amount of
|
||||
// real time to complete. If a server implementation has no such housekeeping
|
||||
// considerations, CHECK is equivalent to NOOP.
|
||||
Check() error
|
||||
|
||||
// ListMessages returns a list of messages. seqset must be interpreted as UIDs
|
||||
// if uid is set to true and as message sequence numbers otherwise. See RFC
|
||||
// 3501 section 6.4.5 for a list of items that can be requested.
|
||||
//
|
||||
// Messages must be sent to ch. When the function returns, ch must be closed.
|
||||
ListMessages(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error
|
||||
|
||||
// SearchMessages searches messages. The returned list must contain UIDs if
|
||||
// uid is set to true, or sequence numbers otherwise.
|
||||
SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error)
|
||||
|
||||
// CreateMessage appends a new message to this mailbox. The \Recent flag will
|
||||
// be added no matter flags is empty or not. If date is nil, the current time
|
||||
// will be used.
|
||||
//
|
||||
// If the Backend implements Updater, it must notify the client immediately
|
||||
// via a mailbox update.
|
||||
CreateMessage(flags []string, date time.Time, body imap.Literal) error
|
||||
|
||||
// UpdateMessagesFlags alters flags for the specified message(s).
|
||||
//
|
||||
// If the Backend implements Updater, it must notify the client immediately
|
||||
// via a message update.
|
||||
UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, operation imap.FlagsOp, flags []string) error
|
||||
|
||||
// CopyMessages copies the specified message(s) to the end of the specified
|
||||
// destination mailbox. The flags and internal date of the message(s) SHOULD
|
||||
// be preserved, and the Recent flag SHOULD be set, in the copy.
|
||||
//
|
||||
// If the destination mailbox does not exist, a server SHOULD return an error.
|
||||
// It SHOULD NOT automatically create the mailbox.
|
||||
//
|
||||
// If the Backend implements Updater, it must notify the client immediately
|
||||
// via a mailbox update.
|
||||
CopyMessages(uid bool, seqset *imap.SeqSet, dest string) error
|
||||
|
||||
// Expunge permanently removes all messages that have the \Deleted flag set
|
||||
// from the currently selected mailbox.
|
||||
//
|
||||
// If the Backend implements Updater, it must notify the client immediately
|
||||
// via an expunge update.
|
||||
Expunge() error
|
||||
}
|
19
vendor/github.com/emersion/go-imap/backend/move.go
generated
vendored
Normal file
19
vendor/github.com/emersion/go-imap/backend/move.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// MoveMailbox is a mailbox that supports moving messages.
|
||||
type MoveMailbox interface {
|
||||
Mailbox
|
||||
|
||||
// Move the specified message(s) to the end of the specified destination
|
||||
// mailbox. This means that a new message is created in the target mailbox
|
||||
// with a new UID, the original message is removed from the source mailbox,
|
||||
// and it appears to the client as a single action.
|
||||
//
|
||||
// If the destination mailbox does not exist, a server SHOULD return an error.
|
||||
// It SHOULD NOT automatically create the mailbox.
|
||||
MoveMessages(uid bool, seqset *imap.SeqSet, dest string) error
|
||||
}
|
98
vendor/github.com/emersion/go-imap/backend/updates.go
generated
vendored
Normal file
98
vendor/github.com/emersion/go-imap/backend/updates.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Update contains user and mailbox information about an unilateral backend
|
||||
// update.
|
||||
type Update interface {
|
||||
// The user targeted by this update. If empty, all connected users will
|
||||
// be notified.
|
||||
Username() string
|
||||
// The mailbox targeted by this update. If empty, the update targets all
|
||||
// mailboxes.
|
||||
Mailbox() string
|
||||
// Done returns a channel that is closed when the update has been broadcast to
|
||||
// all clients.
|
||||
Done() chan struct{}
|
||||
}
|
||||
|
||||
// NewUpdate creates a new update.
|
||||
func NewUpdate(username, mailbox string) Update {
|
||||
return &update{
|
||||
username: username,
|
||||
mailbox: mailbox,
|
||||
}
|
||||
}
|
||||
|
||||
type update struct {
|
||||
username string
|
||||
mailbox string
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (u *update) Username() string {
|
||||
return u.username
|
||||
}
|
||||
|
||||
func (u *update) Mailbox() string {
|
||||
return u.mailbox
|
||||
}
|
||||
|
||||
func (u *update) Done() chan struct{} {
|
||||
if u.done == nil {
|
||||
u.done = make(chan struct{})
|
||||
}
|
||||
return u.done
|
||||
}
|
||||
|
||||
// StatusUpdate is a status update. See RFC 3501 section 7.1 for a list of
|
||||
// status responses.
|
||||
type StatusUpdate struct {
|
||||
Update
|
||||
*imap.StatusResp
|
||||
}
|
||||
|
||||
// MailboxUpdate is a mailbox update.
|
||||
type MailboxUpdate struct {
|
||||
Update
|
||||
*imap.MailboxStatus
|
||||
}
|
||||
|
||||
// MailboxInfoUpdate is a maiblox info update.
|
||||
type MailboxInfoUpdate struct {
|
||||
Update
|
||||
*imap.MailboxInfo
|
||||
}
|
||||
|
||||
// MessageUpdate is a message update.
|
||||
type MessageUpdate struct {
|
||||
Update
|
||||
*imap.Message
|
||||
}
|
||||
|
||||
// ExpungeUpdate is an expunge update.
|
||||
type ExpungeUpdate struct {
|
||||
Update
|
||||
SeqNum uint32
|
||||
}
|
||||
|
||||
// BackendUpdater is a Backend that implements Updater is able to send
|
||||
// unilateral backend updates. Backends not implementing this interface don't
|
||||
// correctly send unilateral updates, for instance if a user logs in from two
|
||||
// connections and deletes a message from one of them, the over is not aware
|
||||
// that such a mesage has been deleted. More importantly, backends implementing
|
||||
// Updater can notify the user for external updates such as new message
|
||||
// notifications.
|
||||
type BackendUpdater interface {
|
||||
// Updates returns a set of channels where updates are sent to.
|
||||
Updates() <-chan Update
|
||||
}
|
||||
|
||||
// MailboxPoller is a Mailbox that is able to poll updates for new messages or
|
||||
// message status updates during a period of inactivity.
|
||||
type MailboxPoller interface {
|
||||
// Poll requests mailbox updates.
|
||||
Poll() error
|
||||
}
|
92
vendor/github.com/emersion/go-imap/backend/user.go
generated
vendored
Normal file
92
vendor/github.com/emersion/go-imap/backend/user.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
package backend
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrNoSuchMailbox is returned by User.GetMailbox, User.DeleteMailbox and
|
||||
// User.RenameMailbox when retrieving, deleting or renaming a mailbox that
|
||||
// doesn't exist.
|
||||
ErrNoSuchMailbox = errors.New("No such mailbox")
|
||||
// ErrMailboxAlreadyExists is returned by User.CreateMailbox and
|
||||
// User.RenameMailbox when creating or renaming mailbox that already exists.
|
||||
ErrMailboxAlreadyExists = errors.New("Mailbox already exists")
|
||||
)
|
||||
|
||||
// User represents a user in the mail storage system. A user operation always
|
||||
// deals with mailboxes.
|
||||
type User interface {
|
||||
// Username returns this user's username.
|
||||
Username() string
|
||||
|
||||
// ListMailboxes returns a list of mailboxes belonging to this user. If
|
||||
// subscribed is set to true, only returns subscribed mailboxes.
|
||||
ListMailboxes(subscribed bool) ([]Mailbox, error)
|
||||
|
||||
// GetMailbox returns a mailbox. If it doesn't exist, it returns
|
||||
// ErrNoSuchMailbox.
|
||||
GetMailbox(name string) (Mailbox, error)
|
||||
|
||||
// CreateMailbox creates a new mailbox.
|
||||
//
|
||||
// If the mailbox already exists, an error must be returned. If the mailbox
|
||||
// name is suffixed with the server's hierarchy separator character, this is a
|
||||
// declaration that the client intends to create mailbox names under this name
|
||||
// in the hierarchy.
|
||||
//
|
||||
// If the server's hierarchy separator character appears elsewhere in the
|
||||
// name, the server SHOULD create any superior hierarchical names that are
|
||||
// needed for the CREATE command to be successfully completed. In other
|
||||
// words, an attempt to create "foo/bar/zap" on a server in which "/" is the
|
||||
// hierarchy separator character SHOULD create foo/ and foo/bar/ if they do
|
||||
// not already exist.
|
||||
//
|
||||
// If a new mailbox is created with the same name as a mailbox which was
|
||||
// deleted, its unique identifiers MUST be greater than any unique identifiers
|
||||
// used in the previous incarnation of the mailbox UNLESS the new incarnation
|
||||
// has a different unique identifier validity value.
|
||||
CreateMailbox(name string) error
|
||||
|
||||
// DeleteMailbox permanently remove the mailbox with the given name. It is an
|
||||
// error to // attempt to delete INBOX or a mailbox name that does not exist.
|
||||
//
|
||||
// The DELETE command MUST NOT remove inferior hierarchical names. For
|
||||
// example, if a mailbox "foo" has an inferior "foo.bar" (assuming "." is the
|
||||
// hierarchy delimiter character), removing "foo" MUST NOT remove "foo.bar".
|
||||
//
|
||||
// The value of the highest-used unique identifier of the deleted mailbox MUST
|
||||
// be preserved so that a new mailbox created with the same name will not
|
||||
// reuse the identifiers of the former incarnation, UNLESS the new incarnation
|
||||
// has a different unique identifier validity value.
|
||||
DeleteMailbox(name string) error
|
||||
|
||||
// RenameMailbox changes the name of a mailbox. It is an error to attempt to
|
||||
// rename from a mailbox name that does not exist or to a mailbox name that
|
||||
// already exists.
|
||||
//
|
||||
// If the name has inferior hierarchical names, then the inferior hierarchical
|
||||
// names MUST also be renamed. For example, a rename of "foo" to "zap" will
|
||||
// rename "foo/bar" (assuming "/" is the hierarchy delimiter character) to
|
||||
// "zap/bar".
|
||||
//
|
||||
// If the server's hierarchy separator character appears in the name, the
|
||||
// server SHOULD create any superior hierarchical names that are needed for
|
||||
// the RENAME command to complete successfully. In other words, an attempt to
|
||||
// rename "foo/bar/zap" to baz/rag/zowie on a server in which "/" is the
|
||||
// hierarchy separator character SHOULD create baz/ and baz/rag/ if they do
|
||||
// not already exist.
|
||||
//
|
||||
// The value of the highest-used unique identifier of the old mailbox name
|
||||
// MUST be preserved so that a new mailbox created with the same name will not
|
||||
// reuse the identifiers of the former incarnation, UNLESS the new incarnation
|
||||
// has a different unique identifier validity value.
|
||||
//
|
||||
// Renaming INBOX is permitted, and has special behavior. It moves all
|
||||
// messages in INBOX to a new mailbox with the given name, leaving INBOX
|
||||
// empty. If the server implementation supports inferior hierarchical names
|
||||
// of INBOX, these are unaffected by a rename of INBOX.
|
||||
RenameMailbox(existingName, newName string) error
|
||||
|
||||
// Logout is called when this User will no longer be used, likely because the
|
||||
// client closed the connection.
|
||||
Logout() error
|
||||
}
|
689
vendor/github.com/emersion/go-imap/client/client.go
generated
vendored
Normal file
689
vendor/github.com/emersion/go-imap/client/client.go
generated
vendored
Normal file
@ -0,0 +1,689 @@
|
||||
// Package client provides an IMAP client.
|
||||
//
|
||||
// It is not safe to use the same Client from multiple goroutines. In general,
|
||||
// the IMAP protocol doesn't make it possible to send multiple independent
|
||||
// IMAP commands on the same connection.
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
// errClosed is used when a connection is closed while waiting for a command
|
||||
// response.
|
||||
var errClosed = fmt.Errorf("imap: connection closed")
|
||||
|
||||
// errUnregisterHandler is returned by a response handler to unregister itself.
|
||||
var errUnregisterHandler = fmt.Errorf("imap: unregister handler")
|
||||
|
||||
// Update is an unilateral server update.
|
||||
type Update interface {
|
||||
update()
|
||||
}
|
||||
|
||||
// StatusUpdate is delivered when a status update is received.
|
||||
type StatusUpdate struct {
|
||||
Status *imap.StatusResp
|
||||
}
|
||||
|
||||
func (u *StatusUpdate) update() {}
|
||||
|
||||
// MailboxUpdate is delivered when a mailbox status changes.
|
||||
type MailboxUpdate struct {
|
||||
Mailbox *imap.MailboxStatus
|
||||
}
|
||||
|
||||
func (u *MailboxUpdate) update() {}
|
||||
|
||||
// ExpungeUpdate is delivered when a message is deleted.
|
||||
type ExpungeUpdate struct {
|
||||
SeqNum uint32
|
||||
}
|
||||
|
||||
func (u *ExpungeUpdate) update() {}
|
||||
|
||||
// MessageUpdate is delivered when a message attribute changes.
|
||||
type MessageUpdate struct {
|
||||
Message *imap.Message
|
||||
}
|
||||
|
||||
func (u *MessageUpdate) update() {}
|
||||
|
||||
// Client is an IMAP client.
|
||||
type Client struct {
|
||||
conn *imap.Conn
|
||||
isTLS bool
|
||||
serverName string
|
||||
|
||||
loggedOut chan struct{}
|
||||
continues chan<- bool
|
||||
upgrading bool
|
||||
|
||||
handlers []responses.Handler
|
||||
handlersLocker sync.Mutex
|
||||
|
||||
// The current connection state.
|
||||
state imap.ConnState
|
||||
// The selected mailbox, if there is one.
|
||||
mailbox *imap.MailboxStatus
|
||||
// The cached server capabilities.
|
||||
caps map[string]bool
|
||||
// state, mailbox and caps may be accessed in different goroutines. Protect
|
||||
// access.
|
||||
locker sync.Mutex
|
||||
|
||||
// A channel to which unilateral updates from the server will be sent. An
|
||||
// update can be one of: *StatusUpdate, *MailboxUpdate, *MessageUpdate,
|
||||
// *ExpungeUpdate. Note that blocking this channel blocks the whole client,
|
||||
// so it's recommended to use a separate goroutine and a buffered channel to
|
||||
// prevent deadlocks.
|
||||
Updates chan<- Update
|
||||
|
||||
// ErrorLog specifies an optional logger for errors accepting connections and
|
||||
// unexpected behavior from handlers. By default, logging goes to os.Stderr
|
||||
// via the log package's standard logger. The logger must be safe to use
|
||||
// simultaneously from multiple goroutines.
|
||||
ErrorLog imap.Logger
|
||||
|
||||
// Timeout specifies a maximum amount of time to wait on a command.
|
||||
//
|
||||
// A Timeout of zero means no timeout. This is the default.
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func (c *Client) registerHandler(h responses.Handler) {
|
||||
if h == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.handlersLocker.Lock()
|
||||
c.handlers = append(c.handlers, h)
|
||||
c.handlersLocker.Unlock()
|
||||
}
|
||||
|
||||
func (c *Client) handle(resp imap.Resp) error {
|
||||
c.handlersLocker.Lock()
|
||||
for i := len(c.handlers) - 1; i >= 0; i-- {
|
||||
if err := c.handlers[i].Handle(resp); err != responses.ErrUnhandled {
|
||||
if err == errUnregisterHandler {
|
||||
c.handlers = append(c.handlers[:i], c.handlers[i+1:]...)
|
||||
err = nil
|
||||
}
|
||||
c.handlersLocker.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.handlersLocker.Unlock()
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
|
||||
func (c *Client) reader() {
|
||||
defer close(c.loggedOut)
|
||||
// Loop while connected.
|
||||
for {
|
||||
connected, err := c.readOnce()
|
||||
if err != nil {
|
||||
c.ErrorLog.Println("error reading response:", err)
|
||||
}
|
||||
if !connected {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) readOnce() (bool, error) {
|
||||
if c.State() == imap.LogoutState {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
resp, err := imap.ReadResp(c.conn.Reader)
|
||||
if err == io.EOF || c.State() == imap.LogoutState {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
if imap.IsParseError(err) {
|
||||
return true, err
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.handle(resp); err != nil && err != responses.ErrUnhandled {
|
||||
c.ErrorLog.Println("cannot handle response ", resp, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *Client) writeReply(reply []byte) error {
|
||||
if _, err := c.conn.Writer.Write(reply); err != nil {
|
||||
return err
|
||||
}
|
||||
// Flush reply
|
||||
return c.conn.Writer.Flush()
|
||||
}
|
||||
|
||||
type handleResult struct {
|
||||
status *imap.StatusResp
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *Client) execute(cmdr imap.Commander, h responses.Handler) (*imap.StatusResp, error) {
|
||||
cmd := cmdr.Command()
|
||||
cmd.Tag = generateTag()
|
||||
|
||||
var replies <-chan []byte
|
||||
if replier, ok := h.(responses.Replier); ok {
|
||||
replies = replier.Replies()
|
||||
}
|
||||
|
||||
if c.Timeout > 0 {
|
||||
err := c.conn.SetDeadline(time.Now().Add(c.Timeout))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// It's possible the client had a timeout set from a previous command, but no
|
||||
// longer does. Ensure we respect that. The zero time means no deadline.
|
||||
if err := c.conn.SetDeadline(time.Time{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we are upgrading.
|
||||
upgrading := c.upgrading
|
||||
|
||||
// Add handler before sending command, to be sure to get the response in time
|
||||
// (in tests, the response is sent right after our command is received, so
|
||||
// sometimes the response was received before the setup of this handler)
|
||||
doneHandle := make(chan handleResult, 1)
|
||||
unregister := make(chan struct{})
|
||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
||||
select {
|
||||
case <-unregister:
|
||||
// If an error occured while sending the command, abort
|
||||
return errUnregisterHandler
|
||||
default:
|
||||
}
|
||||
|
||||
if s, ok := resp.(*imap.StatusResp); ok && s.Tag == cmd.Tag {
|
||||
// This is the command's status response, we're done
|
||||
doneHandle <- handleResult{s, nil}
|
||||
// Special handling of connection upgrading.
|
||||
if upgrading {
|
||||
c.upgrading = false
|
||||
// Wait for upgrade to finish.
|
||||
c.conn.Wait()
|
||||
}
|
||||
// Cancel any pending literal write
|
||||
select {
|
||||
case c.continues <- false:
|
||||
default:
|
||||
}
|
||||
return errUnregisterHandler
|
||||
}
|
||||
|
||||
if h != nil {
|
||||
// Pass the response to the response handler
|
||||
if err := h.Handle(resp); err != nil && err != responses.ErrUnhandled {
|
||||
// If the response handler returns an error, abort
|
||||
doneHandle <- handleResult{nil, err}
|
||||
return errUnregisterHandler
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return responses.ErrUnhandled
|
||||
}))
|
||||
|
||||
// Send the command to the server
|
||||
if err := cmd.WriteTo(c.conn.Writer); err != nil {
|
||||
// Error while sending the command
|
||||
close(unregister)
|
||||
|
||||
if err, ok := err.(imap.LiteralLengthErr); ok {
|
||||
// Expected > Actual
|
||||
// The server is waiting for us to write
|
||||
// more bytes, we don't have them. Run.
|
||||
// Expected < Actual
|
||||
// We are about to send a potentially truncated message, we don't
|
||||
// want this (ths terminating CRLF is not sent at this point).
|
||||
c.conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
// Flush writer if we are upgrading
|
||||
if upgrading {
|
||||
if err := c.conn.Writer.Flush(); err != nil {
|
||||
// Error while sending the command
|
||||
close(unregister)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case reply := <-replies:
|
||||
// Response handler needs to send a reply (Used for AUTHENTICATE)
|
||||
if err := c.writeReply(reply); err != nil {
|
||||
close(unregister)
|
||||
return nil, err
|
||||
}
|
||||
case <-c.loggedOut:
|
||||
// If the connection is closed (such as from an I/O error), ensure we
|
||||
// realize this and don't block waiting on a response that will never
|
||||
// come. loggedOut is a channel that closes when the reader goroutine
|
||||
// ends.
|
||||
close(unregister)
|
||||
return nil, errClosed
|
||||
case result := <-doneHandle:
|
||||
return result.status, result.err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// State returns the current connection state.
|
||||
func (c *Client) State() imap.ConnState {
|
||||
c.locker.Lock()
|
||||
state := c.state
|
||||
c.locker.Unlock()
|
||||
return state
|
||||
}
|
||||
|
||||
// Mailbox returns the selected mailbox. It returns nil if there isn't one.
|
||||
func (c *Client) Mailbox() *imap.MailboxStatus {
|
||||
// c.Mailbox fields are not supposed to change, so we can return the pointer.
|
||||
c.locker.Lock()
|
||||
mbox := c.mailbox
|
||||
c.locker.Unlock()
|
||||
return mbox
|
||||
}
|
||||
|
||||
// SetState sets this connection's internal state.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (c *Client) SetState(state imap.ConnState, mailbox *imap.MailboxStatus) {
|
||||
c.locker.Lock()
|
||||
c.state = state
|
||||
c.mailbox = mailbox
|
||||
c.locker.Unlock()
|
||||
}
|
||||
|
||||
// Execute executes a generic command. cmdr is a value that can be converted to
|
||||
// a raw command and h is a response handler. The function returns when the
|
||||
// command has completed or failed, in this case err is nil. A non-nil err value
|
||||
// indicates a network error.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (c *Client) Execute(cmdr imap.Commander, h responses.Handler) (*imap.StatusResp, error) {
|
||||
return c.execute(cmdr, h)
|
||||
}
|
||||
|
||||
func (c *Client) handleContinuationReqs() {
|
||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
||||
if _, ok := resp.(*imap.ContinuationReq); ok {
|
||||
go func() {
|
||||
c.continues <- true
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
return responses.ErrUnhandled
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Client) gotStatusCaps(args []interface{}) {
|
||||
c.locker.Lock()
|
||||
|
||||
c.caps = make(map[string]bool)
|
||||
for _, cap := range args {
|
||||
if cap, ok := cap.(string); ok {
|
||||
c.caps[cap] = true
|
||||
}
|
||||
}
|
||||
|
||||
c.locker.Unlock()
|
||||
}
|
||||
|
||||
// The server can send unilateral data. This function handles it.
|
||||
func (c *Client) handleUnilateral() {
|
||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
||||
switch resp := resp.(type) {
|
||||
case *imap.StatusResp:
|
||||
if resp.Tag != "*" {
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
|
||||
switch resp.Type {
|
||||
case imap.StatusRespOk, imap.StatusRespNo, imap.StatusRespBad:
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &StatusUpdate{resp}
|
||||
}
|
||||
case imap.StatusRespBye:
|
||||
c.locker.Lock()
|
||||
c.state = imap.LogoutState
|
||||
c.mailbox = nil
|
||||
c.locker.Unlock()
|
||||
|
||||
c.conn.Close()
|
||||
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &StatusUpdate{resp}
|
||||
}
|
||||
default:
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
case *imap.DataResp:
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok {
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "CAPABILITY":
|
||||
c.gotStatusCaps(fields)
|
||||
case "EXISTS":
|
||||
if c.Mailbox() == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if messages, err := imap.ParseNumber(fields[0]); err == nil {
|
||||
c.locker.Lock()
|
||||
c.mailbox.Messages = messages
|
||||
c.locker.Unlock()
|
||||
|
||||
c.mailbox.ItemsLocker.Lock()
|
||||
c.mailbox.Items[imap.StatusMessages] = nil
|
||||
c.mailbox.ItemsLocker.Unlock()
|
||||
}
|
||||
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &MailboxUpdate{c.Mailbox()}
|
||||
}
|
||||
case "RECENT":
|
||||
if c.Mailbox() == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if recent, err := imap.ParseNumber(fields[0]); err == nil {
|
||||
c.locker.Lock()
|
||||
c.mailbox.Recent = recent
|
||||
c.locker.Unlock()
|
||||
|
||||
c.mailbox.ItemsLocker.Lock()
|
||||
c.mailbox.Items[imap.StatusRecent] = nil
|
||||
c.mailbox.ItemsLocker.Unlock()
|
||||
}
|
||||
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &MailboxUpdate{c.Mailbox()}
|
||||
}
|
||||
case "EXPUNGE":
|
||||
seqNum, _ := imap.ParseNumber(fields[0])
|
||||
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &ExpungeUpdate{seqNum}
|
||||
}
|
||||
case "FETCH":
|
||||
seqNum, _ := imap.ParseNumber(fields[0])
|
||||
fields, _ := fields[1].([]interface{})
|
||||
|
||||
msg := &imap.Message{SeqNum: seqNum}
|
||||
if err := msg.Parse(fields); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if c.Updates != nil {
|
||||
c.Updates <- &MessageUpdate{msg}
|
||||
}
|
||||
default:
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
default:
|
||||
return responses.ErrUnhandled
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *Client) handleGreetAndStartReading() error {
|
||||
var greetErr error
|
||||
gotGreet := false
|
||||
|
||||
c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
|
||||
status, ok := resp.(*imap.StatusResp)
|
||||
if !ok {
|
||||
greetErr = fmt.Errorf("invalid greeting received from server: not a status response")
|
||||
return errUnregisterHandler
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
switch status.Type {
|
||||
case imap.StatusRespPreauth:
|
||||
c.state = imap.AuthenticatedState
|
||||
case imap.StatusRespBye:
|
||||
c.state = imap.LogoutState
|
||||
case imap.StatusRespOk:
|
||||
c.state = imap.NotAuthenticatedState
|
||||
default:
|
||||
c.state = imap.LogoutState
|
||||
c.locker.Unlock()
|
||||
greetErr = fmt.Errorf("invalid greeting received from server: %v", status.Type)
|
||||
return errUnregisterHandler
|
||||
}
|
||||
c.locker.Unlock()
|
||||
|
||||
if status.Code == imap.CodeCapability {
|
||||
c.gotStatusCaps(status.Arguments)
|
||||
}
|
||||
|
||||
gotGreet = true
|
||||
return errUnregisterHandler
|
||||
}))
|
||||
|
||||
// call `readOnce` until we get the greeting or an error
|
||||
for !gotGreet {
|
||||
connected, err := c.readOnce()
|
||||
// Check for read errors
|
||||
if err != nil {
|
||||
// return read errors
|
||||
return err
|
||||
}
|
||||
// Check for invalid greet
|
||||
if greetErr != nil {
|
||||
// return read errors
|
||||
return greetErr
|
||||
}
|
||||
// Check if connection was closed.
|
||||
if !connected {
|
||||
// connection closed.
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
// We got the greeting, now start the reader goroutine.
|
||||
go c.reader()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
|
||||
// tunnel.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (c *Client) Upgrade(upgrader imap.ConnUpgrader) error {
|
||||
return c.conn.Upgrade(upgrader)
|
||||
}
|
||||
|
||||
// Writer returns the imap.Writer for this client's connection.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (c *Client) Writer() *imap.Writer {
|
||||
return c.conn.Writer
|
||||
}
|
||||
|
||||
// IsTLS checks if this client's connection has TLS enabled.
|
||||
func (c *Client) IsTLS() bool {
|
||||
return c.isTLS
|
||||
}
|
||||
|
||||
// LoggedOut returns a channel which is closed when the connection to the server
|
||||
// is closed.
|
||||
func (c *Client) LoggedOut() <-chan struct{} {
|
||||
return c.loggedOut
|
||||
}
|
||||
|
||||
// SetDebug defines an io.Writer to which all network activity will be logged.
|
||||
// If nil is provided, network activity will not be logged.
|
||||
func (c *Client) SetDebug(w io.Writer) {
|
||||
// Need to send a command to unblock the reader goroutine.
|
||||
cmd := new(commands.Noop)
|
||||
err := c.Upgrade(func(conn net.Conn) (net.Conn, error) {
|
||||
// Flag connection as in upgrading
|
||||
c.upgrading = true
|
||||
if status, err := c.execute(cmd, nil); err != nil {
|
||||
return nil, err
|
||||
} else if err := status.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait for reader to block.
|
||||
c.conn.WaitReady()
|
||||
|
||||
c.conn.SetDebug(w)
|
||||
return conn, nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("SetDebug:", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// New creates a new client from an existing connection.
|
||||
func New(conn net.Conn) (*Client, error) {
|
||||
continues := make(chan bool)
|
||||
w := imap.NewClientWriter(nil, continues)
|
||||
r := imap.NewReader(nil)
|
||||
|
||||
c := &Client{
|
||||
conn: imap.NewConn(conn, r, w),
|
||||
loggedOut: make(chan struct{}),
|
||||
continues: continues,
|
||||
state: imap.ConnectingState,
|
||||
ErrorLog: log.New(os.Stderr, "imap/client: ", log.LstdFlags),
|
||||
}
|
||||
|
||||
c.handleContinuationReqs()
|
||||
c.handleUnilateral()
|
||||
if err := c.handleGreetAndStartReading(); err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
plusOk, _ := c.Support("LITERAL+")
|
||||
minusOk, _ := c.Support("LITERAL-")
|
||||
// We don't use non-sync literal if it is bigger than 4096 bytes, so
|
||||
// LITERAL- is fine too.
|
||||
c.conn.AllowAsyncLiterals = plusOk || minusOk
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Dial connects to an IMAP server using an unencrypted connection.
|
||||
func Dial(addr string) (*Client, error) {
|
||||
return DialWithDialer(new(net.Dialer), addr)
|
||||
}
|
||||
|
||||
type Dialer interface {
|
||||
// Dial connects to the given address.
|
||||
Dial(network, addr string) (net.Conn, error)
|
||||
}
|
||||
|
||||
// DialWithDialer connects to an IMAP server using an unencrypted connection
|
||||
// using dialer.Dial.
|
||||
//
|
||||
// Among other uses, this allows to apply a dial timeout.
|
||||
func DialWithDialer(dialer Dialer, addr string) (*Client, error) {
|
||||
conn, err := dialer.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We don't return to the caller until we try to receive a greeting. As such,
|
||||
// there is no way to set the client's Timeout for that action. As a
|
||||
// workaround, if the dialer has a timeout set, use that for the connection's
|
||||
// deadline.
|
||||
if netDialer, ok := dialer.(*net.Dialer); ok && netDialer.Timeout > 0 {
|
||||
err := conn.SetDeadline(time.Now().Add(netDialer.Timeout))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c, err := New(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.serverName, _, _ = net.SplitHostPort(addr)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// DialTLS connects to an IMAP server using an encrypted connection.
|
||||
func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
|
||||
return DialWithDialerTLS(new(net.Dialer), addr, tlsConfig)
|
||||
}
|
||||
|
||||
// DialWithDialerTLS connects to an IMAP server using an encrypted connection
|
||||
// using dialer.Dial.
|
||||
//
|
||||
// Among other uses, this allows to apply a dial timeout.
|
||||
func DialWithDialerTLS(dialer Dialer, addr string, tlsConfig *tls.Config) (*Client, error) {
|
||||
conn, err := dialer.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverName, _, _ := net.SplitHostPort(addr)
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
if tlsConfig.ServerName == "" {
|
||||
tlsConfig = tlsConfig.Clone()
|
||||
tlsConfig.ServerName = serverName
|
||||
}
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
|
||||
// We don't return to the caller until we try to receive a greeting. As such,
|
||||
// there is no way to set the client's Timeout for that action. As a
|
||||
// workaround, if the dialer has a timeout set, use that for the connection's
|
||||
// deadline.
|
||||
if netDialer, ok := dialer.(*net.Dialer); ok && netDialer.Timeout > 0 {
|
||||
err := tlsConn.SetDeadline(time.Now().Add(netDialer.Timeout))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c, err := New(tlsConn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.isTLS = true
|
||||
c.serverName = serverName
|
||||
return c, nil
|
||||
}
|
88
vendor/github.com/emersion/go-imap/client/cmd_any.go
generated
vendored
Normal file
88
vendor/github.com/emersion/go-imap/client/cmd_any.go
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
)
|
||||
|
||||
// ErrAlreadyLoggedOut is returned if Logout is called when the client is
|
||||
// already logged out.
|
||||
var ErrAlreadyLoggedOut = errors.New("Already logged out")
|
||||
|
||||
// Capability requests a listing of capabilities that the server supports.
|
||||
// Capabilities are often returned by the server with the greeting or with the
|
||||
// STARTTLS and LOGIN responses, so usually explicitly requesting capabilities
|
||||
// isn't needed.
|
||||
//
|
||||
// Most of the time, Support should be used instead.
|
||||
func (c *Client) Capability() (map[string]bool, error) {
|
||||
cmd := &commands.Capability{}
|
||||
|
||||
if status, err := c.execute(cmd, nil); err != nil {
|
||||
return nil, err
|
||||
} else if err := status.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
caps := c.caps
|
||||
c.locker.Unlock()
|
||||
return caps, nil
|
||||
}
|
||||
|
||||
// Support checks if cap is a capability supported by the server. If the server
|
||||
// hasn't sent its capabilities yet, Support requests them.
|
||||
func (c *Client) Support(cap string) (bool, error) {
|
||||
c.locker.Lock()
|
||||
ok := c.caps != nil
|
||||
c.locker.Unlock()
|
||||
|
||||
// If capabilities are not cached, request them
|
||||
if !ok {
|
||||
if _, err := c.Capability(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
supported := c.caps[cap]
|
||||
c.locker.Unlock()
|
||||
|
||||
return supported, nil
|
||||
}
|
||||
|
||||
// Noop always succeeds and does nothing.
|
||||
//
|
||||
// It can be used as a periodic poll for new messages or message status updates
|
||||
// during a period of inactivity. It can also be used to reset any inactivity
|
||||
// autologout timer on the server.
|
||||
func (c *Client) Noop() error {
|
||||
cmd := new(commands.Noop)
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Logout gracefully closes the connection.
|
||||
func (c *Client) Logout() error {
|
||||
if c.State() == imap.LogoutState {
|
||||
return ErrAlreadyLoggedOut
|
||||
}
|
||||
|
||||
cmd := new(commands.Logout)
|
||||
|
||||
if status, err := c.execute(cmd, nil); err == errClosed {
|
||||
// Server closed connection, that's what we want anyway
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else if status != nil {
|
||||
return status.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
380
vendor/github.com/emersion/go-imap/client/cmd_auth.go
generated
vendored
Normal file
380
vendor/github.com/emersion/go-imap/client/cmd_auth.go
generated
vendored
Normal file
@ -0,0 +1,380 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
// ErrNotLoggedIn is returned if a function that requires the client to be
|
||||
// logged in is called then the client isn't.
|
||||
var ErrNotLoggedIn = errors.New("Not logged in")
|
||||
|
||||
func (c *Client) ensureAuthenticated() error {
|
||||
state := c.State()
|
||||
if state != imap.AuthenticatedState && state != imap.SelectedState {
|
||||
return ErrNotLoggedIn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Select selects a mailbox so that messages in the mailbox can be accessed. Any
|
||||
// currently selected mailbox is deselected before attempting the new selection.
|
||||
// Even if the readOnly parameter is set to false, the server can decide to open
|
||||
// the mailbox in read-only mode.
|
||||
func (c *Client) Select(name string, readOnly bool) (*imap.MailboxStatus, error) {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := &commands.Select{
|
||||
Mailbox: name,
|
||||
ReadOnly: readOnly,
|
||||
}
|
||||
|
||||
mbox := &imap.MailboxStatus{Name: name, Items: make(map[imap.StatusItem]interface{})}
|
||||
res := &responses.Select{
|
||||
Mailbox: mbox,
|
||||
}
|
||||
c.locker.Lock()
|
||||
c.mailbox = mbox
|
||||
c.locker.Unlock()
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
c.locker.Lock()
|
||||
c.mailbox = nil
|
||||
c.locker.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
if err := status.Err(); err != nil {
|
||||
c.locker.Lock()
|
||||
c.mailbox = nil
|
||||
c.locker.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
mbox.ReadOnly = (status.Code == imap.CodeReadOnly)
|
||||
c.state = imap.SelectedState
|
||||
c.locker.Unlock()
|
||||
return mbox, nil
|
||||
}
|
||||
|
||||
// Create creates a mailbox with the given name.
|
||||
func (c *Client) Create(name string) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Create{
|
||||
Mailbox: name,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Delete permanently removes the mailbox with the given name.
|
||||
func (c *Client) Delete(name string) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Delete{
|
||||
Mailbox: name,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Rename changes the name of a mailbox.
|
||||
func (c *Client) Rename(existingName, newName string) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Rename{
|
||||
Existing: existingName,
|
||||
New: newName,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Subscribe adds the specified mailbox name to the server's set of "active" or
|
||||
// "subscribed" mailboxes.
|
||||
func (c *Client) Subscribe(name string) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Subscribe{
|
||||
Mailbox: name,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Unsubscribe removes the specified mailbox name from the server's set of
|
||||
// "active" or "subscribed" mailboxes.
|
||||
func (c *Client) Unsubscribe(name string) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Unsubscribe{
|
||||
Mailbox: name,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// List returns a subset of names from the complete set of all names available
|
||||
// to the client.
|
||||
//
|
||||
// An empty name argument is a special request to return the hierarchy delimiter
|
||||
// and the root name of the name given in the reference. The character "*" is a
|
||||
// wildcard, and matches zero or more characters at this position. The
|
||||
// character "%" is similar to "*", but it does not match a hierarchy delimiter.
|
||||
func (c *Client) List(ref, name string, ch chan *imap.MailboxInfo) error {
|
||||
defer close(ch)
|
||||
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.List{
|
||||
Reference: ref,
|
||||
Mailbox: name,
|
||||
}
|
||||
res := &responses.List{Mailboxes: ch}
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Lsub returns a subset of names from the set of names that the user has
|
||||
// declared as being "active" or "subscribed".
|
||||
func (c *Client) Lsub(ref, name string, ch chan *imap.MailboxInfo) error {
|
||||
defer close(ch)
|
||||
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.List{
|
||||
Reference: ref,
|
||||
Mailbox: name,
|
||||
Subscribed: true,
|
||||
}
|
||||
res := &responses.List{
|
||||
Mailboxes: ch,
|
||||
Subscribed: true,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Status requests the status of the indicated mailbox. It does not change the
|
||||
// currently selected mailbox, nor does it affect the state of any messages in
|
||||
// the queried mailbox.
|
||||
//
|
||||
// See RFC 3501 section 6.3.10 for a list of items that can be requested.
|
||||
func (c *Client) Status(name string, items []imap.StatusItem) (*imap.MailboxStatus, error) {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := &commands.Status{
|
||||
Mailbox: name,
|
||||
Items: items,
|
||||
}
|
||||
res := &responses.Status{
|
||||
Mailbox: new(imap.MailboxStatus),
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Mailbox, status.Err()
|
||||
}
|
||||
|
||||
// Append appends the literal argument as a new message to the end of the
|
||||
// specified destination mailbox. This argument SHOULD be in the format of an
|
||||
// RFC 2822 message. flags and date are optional arguments and can be set to
|
||||
// nil and the empty struct.
|
||||
func (c *Client) Append(mbox string, flags []string, date time.Time, msg imap.Literal) error {
|
||||
if err := c.ensureAuthenticated(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Append{
|
||||
Mailbox: mbox,
|
||||
Flags: flags,
|
||||
Date: date,
|
||||
Message: msg,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Enable requests the server to enable the named extensions. The extensions
|
||||
// which were successfully enabled are returned.
|
||||
//
|
||||
// See RFC 5161 section 3.1.
|
||||
func (c *Client) Enable(caps []string) ([]string, error) {
|
||||
if ok, err := c.Support("ENABLE"); !ok || err != nil {
|
||||
return nil, ErrExtensionUnsupported
|
||||
}
|
||||
|
||||
// ENABLE is invalid if a mailbox has been selected.
|
||||
if c.State() != imap.AuthenticatedState {
|
||||
return nil, ErrNotLoggedIn
|
||||
}
|
||||
|
||||
cmd := &commands.Enable{Caps: caps}
|
||||
res := &responses.Enabled{}
|
||||
|
||||
if status, err := c.Execute(cmd, res); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return res.Caps, status.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) idle(stop <-chan struct{}) error {
|
||||
cmd := &commands.Idle{}
|
||||
|
||||
res := &responses.Idle{
|
||||
Stop: stop,
|
||||
RepliesCh: make(chan []byte, 10),
|
||||
}
|
||||
|
||||
if status, err := c.Execute(cmd, res); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return status.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// IdleOptions holds options for Client.Idle.
|
||||
type IdleOptions struct {
|
||||
// LogoutTimeout is used to avoid being logged out by the server when
|
||||
// idling. Each LogoutTimeout, the IDLE command is restarted. If set to
|
||||
// zero, a default is used. If negative, this behavior is disabled.
|
||||
LogoutTimeout time.Duration
|
||||
// Poll interval when the server doesn't support IDLE. If zero, a default
|
||||
// is used. If negative, polling is always disabled.
|
||||
PollInterval time.Duration
|
||||
}
|
||||
|
||||
// Idle indicates to the server that the client is ready to receive unsolicited
|
||||
// mailbox update messages. When the client wants to send commands again, it
|
||||
// must first close stop.
|
||||
//
|
||||
// If the server doesn't support IDLE, go-imap falls back to polling.
|
||||
func (c *Client) Idle(stop <-chan struct{}, opts *IdleOptions) error {
|
||||
if ok, err := c.Support("IDLE"); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return c.idleFallback(stop, opts)
|
||||
}
|
||||
|
||||
logoutTimeout := 25 * time.Minute
|
||||
if opts != nil {
|
||||
if opts.LogoutTimeout > 0 {
|
||||
logoutTimeout = opts.LogoutTimeout
|
||||
} else if opts.LogoutTimeout < 0 {
|
||||
return c.idle(stop)
|
||||
}
|
||||
}
|
||||
|
||||
t := time.NewTicker(logoutTimeout)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
stopOrRestart := make(chan struct{})
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- c.idle(stopOrRestart)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
close(stopOrRestart)
|
||||
if err := <-done; err != nil {
|
||||
return err
|
||||
}
|
||||
case <-stop:
|
||||
close(stopOrRestart)
|
||||
return <-done
|
||||
case err := <-done:
|
||||
close(stopOrRestart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) idleFallback(stop <-chan struct{}, opts *IdleOptions) error {
|
||||
pollInterval := time.Minute
|
||||
if opts != nil {
|
||||
if opts.PollInterval > 0 {
|
||||
pollInterval = opts.PollInterval
|
||||
} else if opts.PollInterval < 0 {
|
||||
return ErrExtensionUnsupported
|
||||
}
|
||||
}
|
||||
|
||||
t := time.NewTicker(pollInterval)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
if err := c.Noop(); err != nil {
|
||||
return err
|
||||
}
|
||||
case <-stop:
|
||||
return nil
|
||||
case <-c.LoggedOut():
|
||||
return errors.New("disconnected while idling")
|
||||
}
|
||||
}
|
||||
}
|
174
vendor/github.com/emersion/go-imap/client/cmd_noauth.go
generated
vendored
Normal file
174
vendor/github.com/emersion/go-imap/client/cmd_noauth.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAlreadyLoggedIn is returned if Login or Authenticate is called when the
|
||||
// client is already logged in.
|
||||
ErrAlreadyLoggedIn = errors.New("Already logged in")
|
||||
// ErrTLSAlreadyEnabled is returned if StartTLS is called when TLS is already
|
||||
// enabled.
|
||||
ErrTLSAlreadyEnabled = errors.New("TLS is already enabled")
|
||||
// ErrLoginDisabled is returned if Login or Authenticate is called when the
|
||||
// server has disabled authentication. Most of the time, calling enabling TLS
|
||||
// solves the problem.
|
||||
ErrLoginDisabled = errors.New("Login is disabled in current state")
|
||||
)
|
||||
|
||||
// SupportStartTLS checks if the server supports STARTTLS.
|
||||
func (c *Client) SupportStartTLS() (bool, error) {
|
||||
return c.Support("STARTTLS")
|
||||
}
|
||||
|
||||
// StartTLS starts TLS negotiation.
|
||||
func (c *Client) StartTLS(tlsConfig *tls.Config) error {
|
||||
if c.isTLS {
|
||||
return ErrTLSAlreadyEnabled
|
||||
}
|
||||
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = new(tls.Config)
|
||||
}
|
||||
if tlsConfig.ServerName == "" {
|
||||
tlsConfig = tlsConfig.Clone()
|
||||
tlsConfig.ServerName = c.serverName
|
||||
}
|
||||
|
||||
cmd := new(commands.StartTLS)
|
||||
|
||||
err := c.Upgrade(func(conn net.Conn) (net.Conn, error) {
|
||||
// Flag connection as in upgrading
|
||||
c.upgrading = true
|
||||
if status, err := c.execute(cmd, nil); err != nil {
|
||||
return nil, err
|
||||
} else if err := status.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait for reader to block.
|
||||
c.conn.WaitReady()
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Capabilities change when TLS is enabled
|
||||
c.locker.Lock()
|
||||
c.caps = nil
|
||||
c.locker.Unlock()
|
||||
|
||||
return tlsConn, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.isTLS = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportAuth checks if the server supports a given authentication mechanism.
|
||||
func (c *Client) SupportAuth(mech string) (bool, error) {
|
||||
return c.Support("AUTH=" + mech)
|
||||
}
|
||||
|
||||
// Authenticate indicates a SASL authentication mechanism to the server. If the
|
||||
// server supports the requested authentication mechanism, it performs an
|
||||
// authentication protocol exchange to authenticate and identify the client.
|
||||
func (c *Client) Authenticate(auth sasl.Client) error {
|
||||
if c.State() != imap.NotAuthenticatedState {
|
||||
return ErrAlreadyLoggedIn
|
||||
}
|
||||
|
||||
mech, ir, err := auth.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &commands.Authenticate{
|
||||
Mechanism: mech,
|
||||
}
|
||||
|
||||
irOk, err := c.Support("SASL-IR")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if irOk {
|
||||
cmd.InitialResponse = ir
|
||||
}
|
||||
|
||||
res := &responses.Authenticate{
|
||||
Mechanism: auth,
|
||||
InitialResponse: ir,
|
||||
RepliesCh: make(chan []byte, 10),
|
||||
}
|
||||
if irOk {
|
||||
res.InitialResponse = nil
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = status.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
c.state = imap.AuthenticatedState
|
||||
c.caps = nil // Capabilities change when user is logged in
|
||||
c.locker.Unlock()
|
||||
|
||||
if status.Code == "CAPABILITY" {
|
||||
c.gotStatusCaps(status.Arguments)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Login identifies the client to the server and carries the plaintext password
|
||||
// authenticating this user.
|
||||
func (c *Client) Login(username, password string) error {
|
||||
if state := c.State(); state == imap.AuthenticatedState || state == imap.SelectedState {
|
||||
return ErrAlreadyLoggedIn
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
loginDisabled := c.caps != nil && c.caps["LOGINDISABLED"]
|
||||
c.locker.Unlock()
|
||||
if loginDisabled {
|
||||
return ErrLoginDisabled
|
||||
}
|
||||
|
||||
cmd := &commands.Login{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = status.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
c.state = imap.AuthenticatedState
|
||||
c.caps = nil // Capabilities change when user is logged in
|
||||
c.locker.Unlock()
|
||||
|
||||
if status.Code == "CAPABILITY" {
|
||||
c.gotStatusCaps(status.Arguments)
|
||||
}
|
||||
return nil
|
||||
}
|
367
vendor/github.com/emersion/go-imap/client/cmd_selected.go
generated
vendored
Normal file
367
vendor/github.com/emersion/go-imap/client/cmd_selected.go
generated
vendored
Normal file
@ -0,0 +1,367 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoMailboxSelected is returned if a command that requires a mailbox to be
|
||||
// selected is called when there isn't.
|
||||
ErrNoMailboxSelected = errors.New("No mailbox selected")
|
||||
|
||||
// ErrExtensionUnsupported is returned if a command uses a extension that
|
||||
// is not supported by the server.
|
||||
ErrExtensionUnsupported = errors.New("The required extension is not supported by the server")
|
||||
)
|
||||
|
||||
// Check requests a checkpoint of the currently selected mailbox. A checkpoint
|
||||
// refers to any implementation-dependent housekeeping associated with the
|
||||
// mailbox that is not normally executed as part of each command.
|
||||
func (c *Client) Check() error {
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
cmd := new(commands.Check)
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Close permanently removes all messages that have the \Deleted flag set from
|
||||
// the currently selected mailbox, and returns to the authenticated state from
|
||||
// the selected state.
|
||||
func (c *Client) Close() error {
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
cmd := new(commands.Close)
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if err := status.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.locker.Lock()
|
||||
c.state = imap.AuthenticatedState
|
||||
c.mailbox = nil
|
||||
c.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Terminate closes the tcp connection
|
||||
func (c *Client) Terminate() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// Expunge permanently removes all messages that have the \Deleted flag set from
|
||||
// the currently selected mailbox. If ch is not nil, sends sequence IDs of each
|
||||
// deleted message to this channel.
|
||||
func (c *Client) Expunge(ch chan uint32) error {
|
||||
if ch != nil {
|
||||
defer close(ch)
|
||||
}
|
||||
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
cmd := new(commands.Expunge)
|
||||
|
||||
var h responses.Handler
|
||||
if ch != nil {
|
||||
h = &responses.Expunge{SeqNums: ch}
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
func (c *Client) executeSearch(uid bool, criteria *imap.SearchCriteria, charset string) (ids []uint32, status *imap.StatusResp, err error) {
|
||||
if c.State() != imap.SelectedState {
|
||||
err = ErrNoMailboxSelected
|
||||
return
|
||||
}
|
||||
|
||||
var cmd imap.Commander = &commands.Search{
|
||||
Charset: charset,
|
||||
Criteria: criteria,
|
||||
}
|
||||
if uid {
|
||||
cmd = &commands.Uid{Cmd: cmd}
|
||||
}
|
||||
|
||||
res := new(responses.Search)
|
||||
|
||||
status, err = c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err, ids = status.Err(), res.Ids
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) search(uid bool, criteria *imap.SearchCriteria) (ids []uint32, err error) {
|
||||
ids, status, err := c.executeSearch(uid, criteria, "UTF-8")
|
||||
if status != nil && status.Code == imap.CodeBadCharset {
|
||||
// Some servers don't support UTF-8
|
||||
ids, _, err = c.executeSearch(uid, criteria, "US-ASCII")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Search searches the mailbox for messages that match the given searching
|
||||
// criteria. Searching criteria consist of one or more search keys. The response
|
||||
// contains a list of message sequence IDs corresponding to those messages that
|
||||
// match the searching criteria. When multiple keys are specified, the result is
|
||||
// the intersection (AND function) of all the messages that match those keys.
|
||||
// Criteria must be UTF-8 encoded. See RFC 3501 section 6.4.4 for a list of
|
||||
// searching criteria. When no criteria has been set, all messages in the mailbox
|
||||
// will be searched using ALL criteria.
|
||||
func (c *Client) Search(criteria *imap.SearchCriteria) (seqNums []uint32, err error) {
|
||||
return c.search(false, criteria)
|
||||
}
|
||||
|
||||
// UidSearch is identical to Search, but UIDs are returned instead of message
|
||||
// sequence numbers.
|
||||
func (c *Client) UidSearch(criteria *imap.SearchCriteria) (uids []uint32, err error) {
|
||||
return c.search(true, criteria)
|
||||
}
|
||||
|
||||
func (c *Client) fetch(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
|
||||
defer close(ch)
|
||||
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
var cmd imap.Commander = &commands.Fetch{
|
||||
SeqSet: seqset,
|
||||
Items: items,
|
||||
}
|
||||
if uid {
|
||||
cmd = &commands.Uid{Cmd: cmd}
|
||||
}
|
||||
|
||||
res := &responses.Fetch{Messages: ch, SeqSet: seqset, Uid: uid}
|
||||
|
||||
status, err := c.execute(cmd, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Fetch retrieves data associated with a message in the mailbox. See RFC 3501
|
||||
// section 6.4.5 for a list of items that can be requested.
|
||||
func (c *Client) Fetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
|
||||
return c.fetch(false, seqset, items, ch)
|
||||
}
|
||||
|
||||
// UidFetch is identical to Fetch, but seqset is interpreted as containing
|
||||
// unique identifiers instead of message sequence numbers.
|
||||
func (c *Client) UidFetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
|
||||
return c.fetch(true, seqset, items, ch)
|
||||
}
|
||||
|
||||
func (c *Client) store(uid bool, seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
|
||||
if ch != nil {
|
||||
defer close(ch)
|
||||
}
|
||||
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
// TODO: this could break extensions (this only works when item is FLAGS)
|
||||
if fields, ok := value.([]interface{}); ok {
|
||||
for i, field := range fields {
|
||||
if s, ok := field.(string); ok {
|
||||
fields[i] = imap.RawString(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If ch is nil, the updated values are data which will be lost, so don't
|
||||
// retrieve it.
|
||||
if ch == nil {
|
||||
op, _, err := imap.ParseFlagsOp(item)
|
||||
if err == nil {
|
||||
item = imap.FormatFlagsOp(op, true)
|
||||
}
|
||||
}
|
||||
|
||||
var cmd imap.Commander = &commands.Store{
|
||||
SeqSet: seqset,
|
||||
Item: item,
|
||||
Value: value,
|
||||
}
|
||||
if uid {
|
||||
cmd = &commands.Uid{Cmd: cmd}
|
||||
}
|
||||
|
||||
var h responses.Handler
|
||||
if ch != nil {
|
||||
h = &responses.Fetch{Messages: ch, SeqSet: seqset, Uid: uid}
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Store alters data associated with a message in the mailbox. If ch is not nil,
|
||||
// the updated value of the data will be sent to this channel. See RFC 3501
|
||||
// section 6.4.6 for a list of items that can be updated.
|
||||
func (c *Client) Store(seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
|
||||
return c.store(false, seqset, item, value, ch)
|
||||
}
|
||||
|
||||
// UidStore is identical to Store, but seqset is interpreted as containing
|
||||
// unique identifiers instead of message sequence numbers.
|
||||
func (c *Client) UidStore(seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
|
||||
return c.store(true, seqset, item, value, ch)
|
||||
}
|
||||
|
||||
func (c *Client) copy(uid bool, seqset *imap.SeqSet, dest string) error {
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
var cmd imap.Commander = &commands.Copy{
|
||||
SeqSet: seqset,
|
||||
Mailbox: dest,
|
||||
}
|
||||
if uid {
|
||||
cmd = &commands.Uid{Cmd: cmd}
|
||||
}
|
||||
|
||||
status, err := c.execute(cmd, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return status.Err()
|
||||
}
|
||||
|
||||
// Copy copies the specified message(s) to the end of the specified destination
|
||||
// mailbox.
|
||||
func (c *Client) Copy(seqset *imap.SeqSet, dest string) error {
|
||||
return c.copy(false, seqset, dest)
|
||||
}
|
||||
|
||||
// UidCopy is identical to Copy, but seqset is interpreted as containing unique
|
||||
// identifiers instead of message sequence numbers.
|
||||
func (c *Client) UidCopy(seqset *imap.SeqSet, dest string) error {
|
||||
return c.copy(true, seqset, dest)
|
||||
}
|
||||
|
||||
func (c *Client) move(uid bool, seqset *imap.SeqSet, dest string) error {
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
if ok, err := c.Support("MOVE"); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return c.moveFallback(uid, seqset, dest)
|
||||
}
|
||||
|
||||
var cmd imap.Commander = &commands.Move{
|
||||
SeqSet: seqset,
|
||||
Mailbox: dest,
|
||||
}
|
||||
if uid {
|
||||
cmd = &commands.Uid{Cmd: cmd}
|
||||
}
|
||||
|
||||
if status, err := c.Execute(cmd, nil); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return status.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// moveFallback uses COPY, STORE and EXPUNGE for servers which don't support
|
||||
// MOVE.
|
||||
func (c *Client) moveFallback(uid bool, seqset *imap.SeqSet, dest string) error {
|
||||
item := imap.FormatFlagsOp(imap.AddFlags, true)
|
||||
flags := []interface{}{imap.DeletedFlag}
|
||||
if uid {
|
||||
if err := c.UidCopy(seqset, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.UidStore(seqset, item, flags, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := c.Copy(seqset, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.Store(seqset, item, flags, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.Expunge(nil)
|
||||
}
|
||||
|
||||
// Move moves the specified message(s) to the end of the specified destination
|
||||
// mailbox.
|
||||
//
|
||||
// If the server doesn't support the MOVE extension defined in RFC 6851,
|
||||
// go-imap will fallback to copy, store and expunge.
|
||||
func (c *Client) Move(seqset *imap.SeqSet, dest string) error {
|
||||
return c.move(false, seqset, dest)
|
||||
}
|
||||
|
||||
// UidMove is identical to Move, but seqset is interpreted as containing unique
|
||||
// identifiers instead of message sequence numbers.
|
||||
func (c *Client) UidMove(seqset *imap.SeqSet, dest string) error {
|
||||
return c.move(true, seqset, dest)
|
||||
}
|
||||
|
||||
// Unselect frees server's resources associated with the selected mailbox and
|
||||
// returns the server to the authenticated state. This command performs the same
|
||||
// actions as Close, except that no messages are permanently removed from the
|
||||
// currently selected mailbox.
|
||||
//
|
||||
// If client does not support the UNSELECT extension, ErrExtensionUnsupported
|
||||
// is returned.
|
||||
func (c *Client) Unselect() error {
|
||||
if ok, err := c.Support("UNSELECT"); !ok || err != nil {
|
||||
return ErrExtensionUnsupported
|
||||
}
|
||||
|
||||
if c.State() != imap.SelectedState {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
cmd := &commands.Unselect{}
|
||||
if status, err := c.Execute(cmd, nil); err != nil {
|
||||
return err
|
||||
} else if err := status.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.SetState(imap.AuthenticatedState, nil)
|
||||
return nil
|
||||
}
|
24
vendor/github.com/emersion/go-imap/client/tag.go
generated
vendored
Normal file
24
vendor/github.com/emersion/go-imap/client/tag.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
func randomString(n int) (string, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func generateTag() string {
|
||||
tag, err := randomString(4)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tag
|
||||
}
|
57
vendor/github.com/emersion/go-imap/command.go
generated
vendored
Normal file
57
vendor/github.com/emersion/go-imap/command.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A value that can be converted to a command.
|
||||
type Commander interface {
|
||||
Command() *Command
|
||||
}
|
||||
|
||||
// A command.
|
||||
type Command struct {
|
||||
// The command tag. It acts as a unique identifier for this command. If empty,
|
||||
// the command is untagged.
|
||||
Tag string
|
||||
// The command name.
|
||||
Name string
|
||||
// The command arguments.
|
||||
Arguments []interface{}
|
||||
}
|
||||
|
||||
// Implements the Commander interface.
|
||||
func (cmd *Command) Command() *Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (cmd *Command) WriteTo(w *Writer) error {
|
||||
tag := cmd.Tag
|
||||
if tag == "" {
|
||||
tag = "*"
|
||||
}
|
||||
|
||||
fields := []interface{}{RawString(tag), RawString(cmd.Name)}
|
||||
fields = append(fields, cmd.Arguments...)
|
||||
return w.writeLine(fields...)
|
||||
}
|
||||
|
||||
// Parse a command from fields.
|
||||
func (cmd *Command) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("imap: cannot parse command: no enough fields")
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if cmd.Tag, ok = fields[0].(string); !ok {
|
||||
return errors.New("imap: cannot parse command: invalid tag")
|
||||
}
|
||||
if cmd.Name, ok = fields[1].(string); !ok {
|
||||
return errors.New("imap: cannot parse command: invalid name")
|
||||
}
|
||||
cmd.Name = strings.ToUpper(cmd.Name) // Command names are case-insensitive
|
||||
|
||||
cmd.Arguments = fields[2:]
|
||||
return nil
|
||||
}
|
93
vendor/github.com/emersion/go-imap/commands/append.go
generated
vendored
Normal file
93
vendor/github.com/emersion/go-imap/commands/append.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Append is an APPEND command, as defined in RFC 3501 section 6.3.11.
|
||||
type Append struct {
|
||||
Mailbox string
|
||||
Flags []string
|
||||
Date time.Time
|
||||
Message imap.Literal
|
||||
}
|
||||
|
||||
func (cmd *Append) Command() *imap.Command {
|
||||
var args []interface{}
|
||||
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
args = append(args, imap.FormatMailboxName(mailbox))
|
||||
|
||||
if cmd.Flags != nil {
|
||||
flags := make([]interface{}, len(cmd.Flags))
|
||||
for i, flag := range cmd.Flags {
|
||||
flags[i] = imap.RawString(flag)
|
||||
}
|
||||
args = append(args, flags)
|
||||
}
|
||||
|
||||
if !cmd.Date.IsZero() {
|
||||
args = append(args, cmd.Date)
|
||||
}
|
||||
|
||||
args = append(args, cmd.Message)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "APPEND",
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Append) Parse(fields []interface{}) (err error) {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
// Parse mailbox name
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
// Parse message literal
|
||||
litIndex := len(fields) - 1
|
||||
var ok bool
|
||||
if cmd.Message, ok = fields[litIndex].(imap.Literal); !ok {
|
||||
return errors.New("Message must be a literal")
|
||||
}
|
||||
|
||||
// Remaining fields a optional
|
||||
fields = fields[1:litIndex]
|
||||
if len(fields) > 0 {
|
||||
// Parse flags list
|
||||
if flags, ok := fields[0].([]interface{}); ok {
|
||||
if cmd.Flags, err = imap.ParseStringList(flags); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, flag := range cmd.Flags {
|
||||
cmd.Flags[i] = imap.CanonicalFlag(flag)
|
||||
}
|
||||
|
||||
fields = fields[1:]
|
||||
}
|
||||
|
||||
// Parse date
|
||||
if len(fields) > 0 {
|
||||
if date, ok := fields[0].(string); !ok {
|
||||
return errors.New("Date must be a string")
|
||||
} else if cmd.Date, err = time.Parse(imap.DateTimeLayout, date); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
124
vendor/github.com/emersion/go-imap/commands/authenticate.go
generated
vendored
Normal file
124
vendor/github.com/emersion/go-imap/commands/authenticate.go
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// AuthenticateConn is a connection that supports IMAP authentication.
|
||||
type AuthenticateConn interface {
|
||||
io.Reader
|
||||
|
||||
// WriteResp writes an IMAP response to this connection.
|
||||
WriteResp(res imap.WriterTo) error
|
||||
}
|
||||
|
||||
// Authenticate is an AUTHENTICATE command, as defined in RFC 3501 section
|
||||
// 6.2.2.
|
||||
type Authenticate struct {
|
||||
Mechanism string
|
||||
InitialResponse []byte
|
||||
}
|
||||
|
||||
func (cmd *Authenticate) Command() *imap.Command {
|
||||
args := []interface{}{imap.RawString(cmd.Mechanism)}
|
||||
if cmd.InitialResponse != nil {
|
||||
var encodedResponse string
|
||||
if len(cmd.InitialResponse) == 0 {
|
||||
// Empty initial response should be encoded as "=", not empty
|
||||
// string.
|
||||
encodedResponse = "="
|
||||
} else {
|
||||
encodedResponse = base64.StdEncoding.EncodeToString(cmd.InitialResponse)
|
||||
}
|
||||
|
||||
args = append(args, imap.RawString(encodedResponse))
|
||||
}
|
||||
return &imap.Command{
|
||||
Name: "AUTHENTICATE",
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Authenticate) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("Not enough arguments")
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if cmd.Mechanism, ok = fields[0].(string); !ok {
|
||||
return errors.New("Mechanism must be a string")
|
||||
}
|
||||
cmd.Mechanism = strings.ToUpper(cmd.Mechanism)
|
||||
|
||||
if len(fields) != 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
encodedResponse, ok := fields[1].(string)
|
||||
if !ok {
|
||||
return errors.New("Initial response must be a string")
|
||||
}
|
||||
if encodedResponse == "=" {
|
||||
cmd.InitialResponse = []byte{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
cmd.InitialResponse, err = base64.StdEncoding.DecodeString(encodedResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, conn AuthenticateConn) error {
|
||||
sasl, ok := mechanisms[cmd.Mechanism]
|
||||
if !ok {
|
||||
return errors.New("Unsupported mechanism")
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(conn)
|
||||
|
||||
response := cmd.InitialResponse
|
||||
for {
|
||||
challenge, done, err := sasl.Next(response)
|
||||
if err != nil || done {
|
||||
return err
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(challenge)
|
||||
cont := &imap.ContinuationReq{Info: encoded}
|
||||
if err := conn.WriteResp(cont); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !scanner.Scan() {
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New("unexpected EOF")
|
||||
}
|
||||
|
||||
encoded = scanner.Text()
|
||||
if encoded != "" {
|
||||
if encoded == "*" {
|
||||
return &imap.ErrStatusResp{Resp: &imap.StatusResp{
|
||||
Type: imap.StatusRespBad,
|
||||
Info: "negotiation cancelled",
|
||||
}}
|
||||
}
|
||||
response, err = base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/capability.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/capability.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Capability is a CAPABILITY command, as defined in RFC 3501 section 6.1.1.
|
||||
type Capability struct{}
|
||||
|
||||
func (c *Capability) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "CAPABILITY",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Capability) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/check.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/check.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Check is a CHECK command, as defined in RFC 3501 section 6.4.1.
|
||||
type Check struct{}
|
||||
|
||||
func (cmd *Check) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "CHECK",
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Check) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/close.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/close.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Close is a CLOSE command, as defined in RFC 3501 section 6.4.2.
|
||||
type Close struct{}
|
||||
|
||||
func (cmd *Close) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "CLOSE",
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Close) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
2
vendor/github.com/emersion/go-imap/commands/commands.go
generated
vendored
Normal file
2
vendor/github.com/emersion/go-imap/commands/commands.go
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
// Package commands implements IMAP commands defined in RFC 3501.
|
||||
package commands
|
47
vendor/github.com/emersion/go-imap/commands/copy.go
generated
vendored
Normal file
47
vendor/github.com/emersion/go-imap/commands/copy.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Copy is a COPY command, as defined in RFC 3501 section 6.4.7.
|
||||
type Copy struct {
|
||||
SeqSet *imap.SeqSet
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Copy) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "COPY",
|
||||
Arguments: []interface{}{cmd.SeqSet, imap.FormatMailboxName(mailbox)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Copy) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if seqSet, ok := fields[0].(string); !ok {
|
||||
return errors.New("Invalid sequence set")
|
||||
} else if seqSet, err := imap.ParseSeqSet(seqSet); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.SeqSet = seqSet
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[1]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
38
vendor/github.com/emersion/go-imap/commands/create.go
generated
vendored
Normal file
38
vendor/github.com/emersion/go-imap/commands/create.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Create is a CREATE command, as defined in RFC 3501 section 6.3.3.
|
||||
type Create struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Create) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "CREATE",
|
||||
Arguments: []interface{}{mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Create) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
38
vendor/github.com/emersion/go-imap/commands/delete.go
generated
vendored
Normal file
38
vendor/github.com/emersion/go-imap/commands/delete.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Delete is a DELETE command, as defined in RFC 3501 section 6.3.3.
|
||||
type Delete struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Delete) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "DELETE",
|
||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Delete) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
23
vendor/github.com/emersion/go-imap/commands/enable.go
generated
vendored
Normal file
23
vendor/github.com/emersion/go-imap/commands/enable.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An ENABLE command, defined in RFC 5161 section 3.1.
|
||||
type Enable struct {
|
||||
Caps []string
|
||||
}
|
||||
|
||||
func (cmd *Enable) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "ENABLE",
|
||||
Arguments: imap.FormatStringList(cmd.Caps),
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Enable) Parse(fields []interface{}) error {
|
||||
var err error
|
||||
cmd.Caps, err = imap.ParseStringList(fields)
|
||||
return err
|
||||
}
|
16
vendor/github.com/emersion/go-imap/commands/expunge.go
generated
vendored
Normal file
16
vendor/github.com/emersion/go-imap/commands/expunge.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Expunge is an EXPUNGE command, as defined in RFC 3501 section 6.4.3.
|
||||
type Expunge struct{}
|
||||
|
||||
func (cmd *Expunge) Command() *imap.Command {
|
||||
return &imap.Command{Name: "EXPUNGE"}
|
||||
}
|
||||
|
||||
func (cmd *Expunge) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
63
vendor/github.com/emersion/go-imap/commands/fetch.go
generated
vendored
Normal file
63
vendor/github.com/emersion/go-imap/commands/fetch.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Fetch is a FETCH command, as defined in RFC 3501 section 6.4.5.
|
||||
type Fetch struct {
|
||||
SeqSet *imap.SeqSet
|
||||
Items []imap.FetchItem
|
||||
}
|
||||
|
||||
func (cmd *Fetch) Command() *imap.Command {
|
||||
// Handle FETCH macros separately as they should not be serialized within parentheses
|
||||
if len(cmd.Items) == 1 && (cmd.Items[0] == imap.FetchAll || cmd.Items[0] == imap.FetchFast || cmd.Items[0] == imap.FetchFull) {
|
||||
return &imap.Command{
|
||||
Name: "FETCH",
|
||||
Arguments: []interface{}{cmd.SeqSet, imap.RawString(cmd.Items[0])},
|
||||
}
|
||||
} else {
|
||||
items := make([]interface{}, len(cmd.Items))
|
||||
for i, item := range cmd.Items {
|
||||
items[i] = imap.RawString(item)
|
||||
}
|
||||
|
||||
return &imap.Command{
|
||||
Name: "FETCH",
|
||||
Arguments: []interface{}{cmd.SeqSet, items},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Fetch) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
var err error
|
||||
if seqset, ok := fields[0].(string); !ok {
|
||||
return errors.New("Sequence set must be an atom")
|
||||
} else if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch items := fields[1].(type) {
|
||||
case string: // A macro or a single item
|
||||
cmd.Items = imap.FetchItem(strings.ToUpper(items)).Expand()
|
||||
case []interface{}: // A list of items
|
||||
cmd.Items = make([]imap.FetchItem, 0, len(items))
|
||||
for _, v := range items {
|
||||
itemStr, _ := v.(string)
|
||||
item := imap.FetchItem(strings.ToUpper(itemStr))
|
||||
cmd.Items = append(cmd.Items, item.Expand()...)
|
||||
}
|
||||
default:
|
||||
return errors.New("Items must be either a string or a list")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
17
vendor/github.com/emersion/go-imap/commands/idle.go
generated
vendored
Normal file
17
vendor/github.com/emersion/go-imap/commands/idle.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An IDLE command.
|
||||
// Se RFC 2177 section 3.
|
||||
type Idle struct{}
|
||||
|
||||
func (cmd *Idle) Command() *imap.Command {
|
||||
return &imap.Command{Name: "IDLE"}
|
||||
}
|
||||
|
||||
func (cmd *Idle) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
60
vendor/github.com/emersion/go-imap/commands/list.go
generated
vendored
Normal file
60
vendor/github.com/emersion/go-imap/commands/list.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// List is a LIST command, as defined in RFC 3501 section 6.3.8. If Subscribed
|
||||
// is set to true, LSUB will be used instead.
|
||||
type List struct {
|
||||
Reference string
|
||||
Mailbox string
|
||||
|
||||
Subscribed bool
|
||||
}
|
||||
|
||||
func (cmd *List) Command() *imap.Command {
|
||||
name := "LIST"
|
||||
if cmd.Subscribed {
|
||||
name = "LSUB"
|
||||
}
|
||||
|
||||
enc := utf7.Encoding.NewEncoder()
|
||||
ref, _ := enc.String(cmd.Reference)
|
||||
mailbox, _ := enc.String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: name,
|
||||
Arguments: []interface{}{ref, mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *List) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
dec := utf7.Encoding.NewDecoder()
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := dec.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
// TODO: canonical mailbox path
|
||||
cmd.Reference = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[1]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := dec.String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
36
vendor/github.com/emersion/go-imap/commands/login.go
generated
vendored
Normal file
36
vendor/github.com/emersion/go-imap/commands/login.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Login is a LOGIN command, as defined in RFC 3501 section 6.2.2.
|
||||
type Login struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (cmd *Login) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "LOGIN",
|
||||
Arguments: []interface{}{cmd.Username, cmd.Password},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Login) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("Not enough arguments")
|
||||
}
|
||||
|
||||
var err error
|
||||
if cmd.Username, err = imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.Password, err = imap.ParseString(fields[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/logout.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/logout.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Logout is a LOGOUT command, as defined in RFC 3501 section 6.1.3.
|
||||
type Logout struct{}
|
||||
|
||||
func (c *Logout) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "LOGOUT",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Logout) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
48
vendor/github.com/emersion/go-imap/commands/move.go
generated
vendored
Normal file
48
vendor/github.com/emersion/go-imap/commands/move.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// A MOVE command.
|
||||
// See RFC 6851 section 3.1.
|
||||
type Move struct {
|
||||
SeqSet *imap.SeqSet
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Move) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "MOVE",
|
||||
Arguments: []interface{}{cmd.SeqSet, mailbox},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Move) Parse(fields []interface{}) (err error) {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
seqset, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Invalid sequence set")
|
||||
}
|
||||
if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mailbox, ok := fields[1].(string)
|
||||
if !ok {
|
||||
return errors.New("Mailbox name must be a string")
|
||||
}
|
||||
if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/noop.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/noop.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Noop is a NOOP command, as defined in RFC 3501 section 6.1.2.
|
||||
type Noop struct{}
|
||||
|
||||
func (c *Noop) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "NOOP",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Noop) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
51
vendor/github.com/emersion/go-imap/commands/rename.go
generated
vendored
Normal file
51
vendor/github.com/emersion/go-imap/commands/rename.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Rename is a RENAME command, as defined in RFC 3501 section 6.3.5.
|
||||
type Rename struct {
|
||||
Existing string
|
||||
New string
|
||||
}
|
||||
|
||||
func (cmd *Rename) Command() *imap.Command {
|
||||
enc := utf7.Encoding.NewEncoder()
|
||||
existingName, _ := enc.String(cmd.Existing)
|
||||
newName, _ := enc.String(cmd.New)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "RENAME",
|
||||
Arguments: []interface{}{imap.FormatMailboxName(existingName), imap.FormatMailboxName(newName)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Rename) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
dec := utf7.Encoding.NewDecoder()
|
||||
|
||||
if existingName, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if existingName, err := dec.String(existingName); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Existing = imap.CanonicalMailboxName(existingName)
|
||||
}
|
||||
|
||||
if newName, err := imap.ParseString(fields[1]); err != nil {
|
||||
return err
|
||||
} else if newName, err := dec.String(newName); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.New = imap.CanonicalMailboxName(newName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
57
vendor/github.com/emersion/go-imap/commands/search.go
generated
vendored
Normal file
57
vendor/github.com/emersion/go-imap/commands/search.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Search is a SEARCH command, as defined in RFC 3501 section 6.4.4.
|
||||
type Search struct {
|
||||
Charset string
|
||||
Criteria *imap.SearchCriteria
|
||||
}
|
||||
|
||||
func (cmd *Search) Command() *imap.Command {
|
||||
var args []interface{}
|
||||
if cmd.Charset != "" {
|
||||
args = append(args, imap.RawString("CHARSET"), imap.RawString(cmd.Charset))
|
||||
}
|
||||
args = append(args, cmd.Criteria.Format()...)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "SEARCH",
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Search) Parse(fields []interface{}) error {
|
||||
if len(fields) == 0 {
|
||||
return errors.New("Missing search criteria")
|
||||
}
|
||||
|
||||
// Parse charset
|
||||
if f, ok := fields[0].(string); ok && strings.EqualFold(f, "CHARSET") {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("Missing CHARSET value")
|
||||
}
|
||||
if cmd.Charset, ok = fields[1].(string); !ok {
|
||||
return errors.New("Charset must be a string")
|
||||
}
|
||||
fields = fields[2:]
|
||||
}
|
||||
|
||||
var charsetReader func(io.Reader) io.Reader
|
||||
charset := strings.ToLower(cmd.Charset)
|
||||
if charset != "utf-8" && charset != "us-ascii" && charset != "" {
|
||||
charsetReader = func(r io.Reader) io.Reader {
|
||||
r, _ = imap.CharsetReader(charset, r)
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Criteria = new(imap.SearchCriteria)
|
||||
return cmd.Criteria.ParseWithCharset(fields, charsetReader)
|
||||
}
|
45
vendor/github.com/emersion/go-imap/commands/select.go
generated
vendored
Normal file
45
vendor/github.com/emersion/go-imap/commands/select.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Select is a SELECT command, as defined in RFC 3501 section 6.3.1. If ReadOnly
|
||||
// is set to true, the EXAMINE command will be used instead.
|
||||
type Select struct {
|
||||
Mailbox string
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
func (cmd *Select) Command() *imap.Command {
|
||||
name := "SELECT"
|
||||
if cmd.ReadOnly {
|
||||
name = "EXAMINE"
|
||||
}
|
||||
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: name,
|
||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Select) Parse(fields []interface{}) error {
|
||||
if len(fields) < 1 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
18
vendor/github.com/emersion/go-imap/commands/starttls.go
generated
vendored
Normal file
18
vendor/github.com/emersion/go-imap/commands/starttls.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// StartTLS is a STARTTLS command, as defined in RFC 3501 section 6.2.1.
|
||||
type StartTLS struct{}
|
||||
|
||||
func (cmd *StartTLS) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "STARTTLS",
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *StartTLS) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
58
vendor/github.com/emersion/go-imap/commands/status.go
generated
vendored
Normal file
58
vendor/github.com/emersion/go-imap/commands/status.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Status is a STATUS command, as defined in RFC 3501 section 6.3.10.
|
||||
type Status struct {
|
||||
Mailbox string
|
||||
Items []imap.StatusItem
|
||||
}
|
||||
|
||||
func (cmd *Status) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
items := make([]interface{}, len(cmd.Items))
|
||||
for i, item := range cmd.Items {
|
||||
items[i] = imap.RawString(item)
|
||||
}
|
||||
|
||||
return &imap.Command{
|
||||
Name: "STATUS",
|
||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox), items},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Status) Parse(fields []interface{}) error {
|
||||
if len(fields) < 2 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
|
||||
}
|
||||
|
||||
items, ok := fields[1].([]interface{})
|
||||
if !ok {
|
||||
return errors.New("STATUS command parameter is not a list")
|
||||
}
|
||||
cmd.Items = make([]imap.StatusItem, len(items))
|
||||
for i, f := range items {
|
||||
if s, ok := f.(string); !ok {
|
||||
return errors.New("Got a non-string field in a STATUS command parameter")
|
||||
} else {
|
||||
cmd.Items[i] = imap.StatusItem(strings.ToUpper(s))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
50
vendor/github.com/emersion/go-imap/commands/store.go
generated
vendored
Normal file
50
vendor/github.com/emersion/go-imap/commands/store.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Store is a STORE command, as defined in RFC 3501 section 6.4.6.
|
||||
type Store struct {
|
||||
SeqSet *imap.SeqSet
|
||||
Item imap.StoreItem
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (cmd *Store) Command() *imap.Command {
|
||||
return &imap.Command{
|
||||
Name: "STORE",
|
||||
Arguments: []interface{}{cmd.SeqSet, imap.RawString(cmd.Item), cmd.Value},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Store) Parse(fields []interface{}) error {
|
||||
if len(fields) < 3 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
seqset, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Invalid sequence set")
|
||||
}
|
||||
var err error
|
||||
if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if item, ok := fields[1].(string); !ok {
|
||||
return errors.New("Item name must be a string")
|
||||
} else {
|
||||
cmd.Item = imap.StoreItem(strings.ToUpper(item))
|
||||
}
|
||||
|
||||
if len(fields[2:]) == 1 {
|
||||
cmd.Value = fields[2]
|
||||
} else {
|
||||
cmd.Value = fields[2:]
|
||||
}
|
||||
return nil
|
||||
}
|
63
vendor/github.com/emersion/go-imap/commands/subscribe.go
generated
vendored
Normal file
63
vendor/github.com/emersion/go-imap/commands/subscribe.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// Subscribe is a SUBSCRIBE command, as defined in RFC 3501 section 6.3.6.
|
||||
type Subscribe struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Subscribe) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "SUBSCRIBE",
|
||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Subscribe) Parse(fields []interface{}) error {
|
||||
if len(fields) < 0 {
|
||||
return errors.New("No enough arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// An UNSUBSCRIBE command.
|
||||
// See RFC 3501 section 6.3.7
|
||||
type Unsubscribe struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
func (cmd *Unsubscribe) Command() *imap.Command {
|
||||
mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "UNSUBSCRIBE",
|
||||
Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Unsubscribe) Parse(fields []interface{}) error {
|
||||
if len(fields) < 0 {
|
||||
return errors.New("No enogh arguments")
|
||||
}
|
||||
|
||||
if mailbox, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
44
vendor/github.com/emersion/go-imap/commands/uid.go
generated
vendored
Normal file
44
vendor/github.com/emersion/go-imap/commands/uid.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// Uid is a UID command, as defined in RFC 3501 section 6.4.8. It wraps another
|
||||
// command (e.g. wrapping a Fetch command will result in a UID FETCH).
|
||||
type Uid struct {
|
||||
Cmd imap.Commander
|
||||
}
|
||||
|
||||
func (cmd *Uid) Command() *imap.Command {
|
||||
inner := cmd.Cmd.Command()
|
||||
|
||||
args := []interface{}{imap.RawString(inner.Name)}
|
||||
args = append(args, inner.Arguments...)
|
||||
|
||||
return &imap.Command{
|
||||
Name: "UID",
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Uid) Parse(fields []interface{}) error {
|
||||
if len(fields) < 0 {
|
||||
return errors.New("No command name specified")
|
||||
}
|
||||
|
||||
name, ok := fields[0].(string)
|
||||
if !ok {
|
||||
return errors.New("Command name must be a string")
|
||||
}
|
||||
|
||||
cmd.Cmd = &imap.Command{
|
||||
Name: strings.ToUpper(name), // Command names are case-insensitive
|
||||
Arguments: fields[1:],
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
17
vendor/github.com/emersion/go-imap/commands/unselect.go
generated
vendored
Normal file
17
vendor/github.com/emersion/go-imap/commands/unselect.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An UNSELECT command.
|
||||
// See RFC 3691 section 2.
|
||||
type Unselect struct{}
|
||||
|
||||
func (cmd *Unselect) Command() *imap.Command {
|
||||
return &imap.Command{Name: "UNSELECT"}
|
||||
}
|
||||
|
||||
func (cmd *Unselect) Parse(fields []interface{}) error {
|
||||
return nil
|
||||
}
|
284
vendor/github.com/emersion/go-imap/conn.go
generated
vendored
Normal file
284
vendor/github.com/emersion/go-imap/conn.go
generated
vendored
Normal file
@ -0,0 +1,284 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A connection state.
|
||||
// See RFC 3501 section 3.
|
||||
type ConnState int
|
||||
|
||||
const (
|
||||
// In the connecting state, the server has not yet sent a greeting and no
|
||||
// command can be issued.
|
||||
ConnectingState = 0
|
||||
|
||||
// In the not authenticated state, the client MUST supply
|
||||
// authentication credentials before most commands will be
|
||||
// permitted. This state is entered when a connection starts
|
||||
// unless the connection has been pre-authenticated.
|
||||
NotAuthenticatedState ConnState = 1 << 0
|
||||
|
||||
// In the authenticated state, the client is authenticated and MUST
|
||||
// select a mailbox to access before commands that affect messages
|
||||
// will be permitted. This state is entered when a
|
||||
// pre-authenticated connection starts, when acceptable
|
||||
// authentication credentials have been provided, after an error in
|
||||
// selecting a mailbox, or after a successful CLOSE command.
|
||||
AuthenticatedState = 1 << 1
|
||||
|
||||
// In a selected state, a mailbox has been selected to access.
|
||||
// This state is entered when a mailbox has been successfully
|
||||
// selected.
|
||||
SelectedState = AuthenticatedState + 1<<2
|
||||
|
||||
// In the logout state, the connection is being terminated. This
|
||||
// state can be entered as a result of a client request (via the
|
||||
// LOGOUT command) or by unilateral action on the part of either
|
||||
// the client or server.
|
||||
LogoutState = 1 << 3
|
||||
|
||||
// ConnectedState is either NotAuthenticatedState, AuthenticatedState or
|
||||
// SelectedState.
|
||||
ConnectedState = NotAuthenticatedState | AuthenticatedState | SelectedState
|
||||
)
|
||||
|
||||
// A function that upgrades a connection.
|
||||
//
|
||||
// This should only be used by libraries implementing an IMAP extension (e.g.
|
||||
// COMPRESS).
|
||||
type ConnUpgrader func(conn net.Conn) (net.Conn, error)
|
||||
|
||||
type Waiter struct {
|
||||
start sync.WaitGroup
|
||||
end sync.WaitGroup
|
||||
finished bool
|
||||
}
|
||||
|
||||
func NewWaiter() *Waiter {
|
||||
w := &Waiter{finished: false}
|
||||
w.start.Add(1)
|
||||
w.end.Add(1)
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *Waiter) Wait() {
|
||||
if !w.finished {
|
||||
// Signal that we are ready for upgrade to continue.
|
||||
w.start.Done()
|
||||
// Wait for upgrade to finish.
|
||||
w.end.Wait()
|
||||
w.finished = true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Waiter) WaitReady() {
|
||||
if !w.finished {
|
||||
// Wait for reader/writer goroutine to be ready for upgrade.
|
||||
w.start.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Waiter) Close() {
|
||||
if !w.finished {
|
||||
// Upgrade is finished, close chanel to release reader/writer
|
||||
w.end.Done()
|
||||
}
|
||||
}
|
||||
|
||||
type LockedWriter struct {
|
||||
lock sync.Mutex
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
// NewLockedWriter - goroutine safe writer.
|
||||
func NewLockedWriter(w io.Writer) io.Writer {
|
||||
return &LockedWriter{writer: w}
|
||||
}
|
||||
|
||||
func (w *LockedWriter) Write(b []byte) (int, error) {
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
return w.writer.Write(b)
|
||||
}
|
||||
|
||||
type debugWriter struct {
|
||||
io.Writer
|
||||
|
||||
local io.Writer
|
||||
remote io.Writer
|
||||
}
|
||||
|
||||
// NewDebugWriter creates a new io.Writer that will write local network activity
|
||||
// to local and remote network activity to remote.
|
||||
func NewDebugWriter(local, remote io.Writer) io.Writer {
|
||||
return &debugWriter{Writer: local, local: local, remote: remote}
|
||||
}
|
||||
|
||||
type multiFlusher struct {
|
||||
flushers []flusher
|
||||
}
|
||||
|
||||
func (mf *multiFlusher) Flush() error {
|
||||
for _, f := range mf.flushers {
|
||||
if err := f.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMultiFlusher(flushers ...flusher) flusher {
|
||||
return &multiFlusher{flushers}
|
||||
}
|
||||
|
||||
// Underlying connection state information.
|
||||
type ConnInfo struct {
|
||||
RemoteAddr net.Addr
|
||||
LocalAddr net.Addr
|
||||
|
||||
// nil if connection is not using TLS.
|
||||
TLS *tls.ConnectionState
|
||||
}
|
||||
|
||||
// An IMAP connection.
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
*Reader
|
||||
*Writer
|
||||
|
||||
br *bufio.Reader
|
||||
bw *bufio.Writer
|
||||
|
||||
waiter *Waiter
|
||||
|
||||
// Print all commands and responses to this io.Writer.
|
||||
debug io.Writer
|
||||
}
|
||||
|
||||
// NewConn creates a new IMAP connection.
|
||||
func NewConn(conn net.Conn, r *Reader, w *Writer) *Conn {
|
||||
c := &Conn{Conn: conn, Reader: r, Writer: w}
|
||||
|
||||
c.init()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Conn) createWaiter() *Waiter {
|
||||
// create new waiter each time.
|
||||
w := NewWaiter()
|
||||
c.waiter = w
|
||||
return w
|
||||
}
|
||||
|
||||
func (c *Conn) init() {
|
||||
r := io.Reader(c.Conn)
|
||||
w := io.Writer(c.Conn)
|
||||
|
||||
if c.debug != nil {
|
||||
localDebug, remoteDebug := c.debug, c.debug
|
||||
if debug, ok := c.debug.(*debugWriter); ok {
|
||||
localDebug, remoteDebug = debug.local, debug.remote
|
||||
}
|
||||
// If local and remote are the same, then we need a LockedWriter.
|
||||
if localDebug == remoteDebug {
|
||||
localDebug = NewLockedWriter(localDebug)
|
||||
remoteDebug = localDebug
|
||||
}
|
||||
|
||||
if localDebug != nil {
|
||||
w = io.MultiWriter(c.Conn, localDebug)
|
||||
}
|
||||
if remoteDebug != nil {
|
||||
r = io.TeeReader(c.Conn, remoteDebug)
|
||||
}
|
||||
}
|
||||
|
||||
if c.br == nil {
|
||||
c.br = bufio.NewReader(r)
|
||||
c.Reader.reader = c.br
|
||||
} else {
|
||||
c.br.Reset(r)
|
||||
}
|
||||
|
||||
if c.bw == nil {
|
||||
c.bw = bufio.NewWriter(w)
|
||||
c.Writer.Writer = c.bw
|
||||
} else {
|
||||
c.bw.Reset(w)
|
||||
}
|
||||
|
||||
if f, ok := c.Conn.(flusher); ok {
|
||||
c.Writer.Writer = struct {
|
||||
io.Writer
|
||||
flusher
|
||||
}{
|
||||
c.bw,
|
||||
newMultiFlusher(c.bw, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Info() *ConnInfo {
|
||||
info := &ConnInfo{
|
||||
RemoteAddr: c.RemoteAddr(),
|
||||
LocalAddr: c.LocalAddr(),
|
||||
}
|
||||
|
||||
tlsConn, ok := c.Conn.(*tls.Conn)
|
||||
if ok {
|
||||
state := tlsConn.ConnectionState()
|
||||
info.TLS = &state
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
return c.Writer.Write(b)
|
||||
}
|
||||
|
||||
// Flush writes any buffered data to the underlying connection.
|
||||
func (c *Conn) Flush() error {
|
||||
return c.Writer.Flush()
|
||||
}
|
||||
|
||||
// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
|
||||
// tunnel.
|
||||
func (c *Conn) Upgrade(upgrader ConnUpgrader) error {
|
||||
// Block reads and writes during the upgrading process
|
||||
w := c.createWaiter()
|
||||
defer w.Close()
|
||||
|
||||
upgraded, err := upgrader(c.Conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Conn = upgraded
|
||||
c.init()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Called by reader/writer goroutines to wait for Upgrade to finish
|
||||
func (c *Conn) Wait() {
|
||||
c.waiter.Wait()
|
||||
}
|
||||
|
||||
// Called by Upgrader to wait for reader/writer goroutines to be ready for
|
||||
// upgrade.
|
||||
func (c *Conn) WaitReady() {
|
||||
c.waiter.WaitReady()
|
||||
}
|
||||
|
||||
// SetDebug defines an io.Writer to which all network activity will be logged.
|
||||
// If nil is provided, network activity will not be logged.
|
||||
func (c *Conn) SetDebug(w io.Writer) {
|
||||
c.debug = w
|
||||
c.init()
|
||||
}
|
71
vendor/github.com/emersion/go-imap/date.go
generated
vendored
Normal file
71
vendor/github.com/emersion/go-imap/date.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Date and time layouts.
|
||||
// Dovecot adds a leading zero to dates:
|
||||
// https://github.com/dovecot/core/blob/4fbd5c5e113078e72f29465ccc96d44955ceadc2/src/lib-imap/imap-date.c#L166
|
||||
// Cyrus adds a leading space to dates:
|
||||
// https://github.com/cyrusimap/cyrus-imapd/blob/1cb805a3bffbdf829df0964f3b802cdc917e76db/lib/times.c#L543
|
||||
// GMail doesn't support leading spaces in dates used in SEARCH commands.
|
||||
const (
|
||||
// Defined in RFC 3501 as date-text on page 83.
|
||||
DateLayout = "_2-Jan-2006"
|
||||
// Defined in RFC 3501 as date-time on page 83.
|
||||
DateTimeLayout = "_2-Jan-2006 15:04:05 -0700"
|
||||
// Defined in RFC 5322 section 3.3, mentioned as env-date in RFC 3501 page 84.
|
||||
envelopeDateTimeLayout = "Mon, 02 Jan 2006 15:04:05 -0700"
|
||||
// Use as an example in RFC 3501 page 54.
|
||||
searchDateLayout = "2-Jan-2006"
|
||||
)
|
||||
|
||||
// time.Time with a specific layout.
|
||||
type (
|
||||
Date time.Time
|
||||
DateTime time.Time
|
||||
envelopeDateTime time.Time
|
||||
searchDate time.Time
|
||||
)
|
||||
|
||||
// Permutations of the layouts defined in RFC 5322, section 3.3.
|
||||
var envelopeDateTimeLayouts = [...]string{
|
||||
envelopeDateTimeLayout, // popular, try it first
|
||||
"_2 Jan 2006 15:04:05 -0700",
|
||||
"_2 Jan 2006 15:04:05 MST",
|
||||
"_2 Jan 2006 15:04 -0700",
|
||||
"_2 Jan 2006 15:04 MST",
|
||||
"_2 Jan 06 15:04:05 -0700",
|
||||
"_2 Jan 06 15:04:05 MST",
|
||||
"_2 Jan 06 15:04 -0700",
|
||||
"_2 Jan 06 15:04 MST",
|
||||
"Mon, _2 Jan 2006 15:04:05 -0700",
|
||||
"Mon, _2 Jan 2006 15:04:05 MST",
|
||||
"Mon, _2 Jan 2006 15:04 -0700",
|
||||
"Mon, _2 Jan 2006 15:04 MST",
|
||||
"Mon, _2 Jan 06 15:04:05 -0700",
|
||||
"Mon, _2 Jan 06 15:04:05 MST",
|
||||
"Mon, _2 Jan 06 15:04 -0700",
|
||||
"Mon, _2 Jan 06 15:04 MST",
|
||||
}
|
||||
|
||||
// TODO: this is a blunt way to strip any trailing CFWS (comment). A sharper
|
||||
// one would strip multiple CFWS, and only if really valid according to
|
||||
// RFC5322.
|
||||
var commentRE = regexp.MustCompile(`[ \t]+\(.*\)$`)
|
||||
|
||||
// Try parsing the date based on the layouts defined in RFC 5322, section 3.3.
|
||||
// Inspired by https://github.com/golang/go/blob/master/src/net/mail/message.go
|
||||
func parseMessageDateTime(maybeDate string) (time.Time, error) {
|
||||
maybeDate = commentRE.ReplaceAllString(maybeDate, "")
|
||||
for _, layout := range envelopeDateTimeLayouts {
|
||||
parsed, err := time.Parse(layout, maybeDate)
|
||||
if err == nil {
|
||||
return parsed, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("date %s could not be parsed", maybeDate)
|
||||
}
|
108
vendor/github.com/emersion/go-imap/imap.go
generated
vendored
Normal file
108
vendor/github.com/emersion/go-imap/imap.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
// Package imap implements IMAP4rev1 (RFC 3501).
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A StatusItem is a mailbox status data item that can be retrieved with a
|
||||
// STATUS command. See RFC 3501 section 6.3.10.
|
||||
type StatusItem string
|
||||
|
||||
const (
|
||||
StatusMessages StatusItem = "MESSAGES"
|
||||
StatusRecent StatusItem = "RECENT"
|
||||
StatusUidNext StatusItem = "UIDNEXT"
|
||||
StatusUidValidity StatusItem = "UIDVALIDITY"
|
||||
StatusUnseen StatusItem = "UNSEEN"
|
||||
|
||||
StatusAppendLimit StatusItem = "APPENDLIMIT"
|
||||
)
|
||||
|
||||
// A FetchItem is a message data item that can be fetched.
|
||||
type FetchItem string
|
||||
|
||||
// List of items that can be fetched.
|
||||
const (
|
||||
// Macros
|
||||
FetchAll FetchItem = "ALL"
|
||||
FetchFast FetchItem = "FAST"
|
||||
FetchFull FetchItem = "FULL"
|
||||
|
||||
// Items
|
||||
FetchBody FetchItem = "BODY"
|
||||
FetchBodyStructure FetchItem = "BODYSTRUCTURE"
|
||||
FetchEnvelope FetchItem = "ENVELOPE"
|
||||
FetchFlags FetchItem = "FLAGS"
|
||||
FetchInternalDate FetchItem = "INTERNALDATE"
|
||||
FetchRFC822 FetchItem = "RFC822"
|
||||
FetchRFC822Header FetchItem = "RFC822.HEADER"
|
||||
FetchRFC822Size FetchItem = "RFC822.SIZE"
|
||||
FetchRFC822Text FetchItem = "RFC822.TEXT"
|
||||
FetchUid FetchItem = "UID"
|
||||
)
|
||||
|
||||
// Expand expands the item if it's a macro.
|
||||
func (item FetchItem) Expand() []FetchItem {
|
||||
switch item {
|
||||
case FetchAll:
|
||||
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size, FetchEnvelope}
|
||||
case FetchFast:
|
||||
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size}
|
||||
case FetchFull:
|
||||
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size, FetchEnvelope, FetchBody}
|
||||
default:
|
||||
return []FetchItem{item}
|
||||
}
|
||||
}
|
||||
|
||||
// FlagsOp is an operation that will be applied on message flags.
|
||||
type FlagsOp string
|
||||
|
||||
const (
|
||||
// SetFlags replaces existing flags by new ones.
|
||||
SetFlags FlagsOp = "FLAGS"
|
||||
// AddFlags adds new flags.
|
||||
AddFlags = "+FLAGS"
|
||||
// RemoveFlags removes existing flags.
|
||||
RemoveFlags = "-FLAGS"
|
||||
)
|
||||
|
||||
// silentOp can be appended to a FlagsOp to prevent the operation from
|
||||
// triggering unilateral message updates.
|
||||
const silentOp = ".SILENT"
|
||||
|
||||
// A StoreItem is a message data item that can be updated.
|
||||
type StoreItem string
|
||||
|
||||
// FormatFlagsOp returns the StoreItem that executes the flags operation op.
|
||||
func FormatFlagsOp(op FlagsOp, silent bool) StoreItem {
|
||||
s := string(op)
|
||||
if silent {
|
||||
s += silentOp
|
||||
}
|
||||
return StoreItem(s)
|
||||
}
|
||||
|
||||
// ParseFlagsOp parses a flags operation from StoreItem.
|
||||
func ParseFlagsOp(item StoreItem) (op FlagsOp, silent bool, err error) {
|
||||
itemStr := string(item)
|
||||
silent = strings.HasSuffix(itemStr, silentOp)
|
||||
if silent {
|
||||
itemStr = strings.TrimSuffix(itemStr, silentOp)
|
||||
}
|
||||
op = FlagsOp(itemStr)
|
||||
|
||||
if op != SetFlags && op != AddFlags && op != RemoveFlags {
|
||||
err = errors.New("Unsupported STORE operation")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CharsetReader, if non-nil, defines a function to generate charset-conversion
|
||||
// readers, converting from the provided charset into UTF-8. Charsets are always
|
||||
// lower-case. utf-8 and us-ascii charsets are handled by default. One of the
|
||||
// the CharsetReader's result values must be non-nil.
|
||||
var CharsetReader func(charset string, r io.Reader) (io.Reader, error)
|
13
vendor/github.com/emersion/go-imap/literal.go
generated
vendored
Normal file
13
vendor/github.com/emersion/go-imap/literal.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// A literal, as defined in RFC 3501 section 4.3.
|
||||
type Literal interface {
|
||||
io.Reader
|
||||
|
||||
// Len returns the number of bytes of the literal.
|
||||
Len() int
|
||||
}
|
8
vendor/github.com/emersion/go-imap/logger.go
generated
vendored
Normal file
8
vendor/github.com/emersion/go-imap/logger.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
package imap
|
||||
|
||||
// Logger is the behaviour used by server/client to
|
||||
// report errors for accepting connections and unexpected behavior from handlers.
|
||||
type Logger interface {
|
||||
Printf(format string, v ...interface{})
|
||||
Println(v ...interface{})
|
||||
}
|
314
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
Normal file
314
vendor/github.com/emersion/go-imap/mailbox.go
generated
vendored
Normal file
@ -0,0 +1,314 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
// The primary mailbox, as defined in RFC 3501 section 5.1.
|
||||
const InboxName = "INBOX"
|
||||
|
||||
// CanonicalMailboxName returns the canonical form of a mailbox name. Mailbox names can be
|
||||
// case-sensitive or case-insensitive depending on the backend implementation.
|
||||
// The special INBOX mailbox is case-insensitive.
|
||||
func CanonicalMailboxName(name string) string {
|
||||
if strings.EqualFold(name, InboxName) {
|
||||
return InboxName
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// Mailbox attributes definied in RFC 3501 section 7.2.2.
|
||||
const (
|
||||
// It is not possible for any child levels of hierarchy to exist under this\
|
||||
// name; no child levels exist now and none can be created in the future.
|
||||
NoInferiorsAttr = "\\Noinferiors"
|
||||
// It is not possible to use this name as a selectable mailbox.
|
||||
NoSelectAttr = "\\Noselect"
|
||||
// The mailbox has been marked "interesting" by the server; the mailbox
|
||||
// probably contains messages that have been added since the last time the
|
||||
// mailbox was selected.
|
||||
MarkedAttr = "\\Marked"
|
||||
// The mailbox does not contain any additional messages since the last time
|
||||
// the mailbox was selected.
|
||||
UnmarkedAttr = "\\Unmarked"
|
||||
)
|
||||
|
||||
// Mailbox attributes defined in RFC 6154 section 2 (SPECIAL-USE extension).
|
||||
const (
|
||||
// This mailbox presents all messages in the user's message store.
|
||||
AllAttr = "\\All"
|
||||
// This mailbox is used to archive messages.
|
||||
ArchiveAttr = "\\Archive"
|
||||
// This mailbox is used to hold draft messages -- typically, messages that are
|
||||
// being composed but have not yet been sent.
|
||||
DraftsAttr = "\\Drafts"
|
||||
// This mailbox presents all messages marked in some way as "important".
|
||||
FlaggedAttr = "\\Flagged"
|
||||
// This mailbox is where messages deemed to be junk mail are held.
|
||||
JunkAttr = "\\Junk"
|
||||
// This mailbox is used to hold copies of messages that have been sent.
|
||||
SentAttr = "\\Sent"
|
||||
// This mailbox is used to hold messages that have been deleted or marked for
|
||||
// deletion.
|
||||
TrashAttr = "\\Trash"
|
||||
)
|
||||
|
||||
// Mailbox attributes defined in RFC 3348 (CHILDREN extension)
|
||||
const (
|
||||
// The presence of this attribute indicates that the mailbox has child
|
||||
// mailboxes.
|
||||
HasChildrenAttr = "\\HasChildren"
|
||||
// The presence of this attribute indicates that the mailbox has no child
|
||||
// mailboxes.
|
||||
HasNoChildrenAttr = "\\HasNoChildren"
|
||||
)
|
||||
|
||||
// This mailbox attribute is a signal that the mailbox contains messages that
|
||||
// are likely important to the user. This attribute is defined in RFC 8457
|
||||
// section 3.
|
||||
const ImportantAttr = "\\Important"
|
||||
|
||||
// Basic mailbox info.
|
||||
type MailboxInfo struct {
|
||||
// The mailbox attributes.
|
||||
Attributes []string
|
||||
// The server's path separator.
|
||||
Delimiter string
|
||||
// The mailbox name.
|
||||
Name string
|
||||
}
|
||||
|
||||
// Parse mailbox info from fields.
|
||||
func (info *MailboxInfo) Parse(fields []interface{}) error {
|
||||
if len(fields) < 3 {
|
||||
return errors.New("Mailbox info needs at least 3 fields")
|
||||
}
|
||||
|
||||
var err error
|
||||
if info.Attributes, err = ParseStringList(fields[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if info.Delimiter, ok = fields[1].(string); !ok {
|
||||
// The delimiter may be specified as NIL, which gets converted to a nil interface.
|
||||
if fields[1] != nil {
|
||||
return errors.New("Mailbox delimiter must be a string")
|
||||
}
|
||||
info.Delimiter = ""
|
||||
}
|
||||
|
||||
if name, err := ParseString(fields[2]); err != nil {
|
||||
return err
|
||||
} else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
|
||||
return err
|
||||
} else {
|
||||
info.Name = CanonicalMailboxName(name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format mailbox info to fields.
|
||||
func (info *MailboxInfo) Format() []interface{} {
|
||||
name, _ := utf7.Encoding.NewEncoder().String(info.Name)
|
||||
attrs := make([]interface{}, len(info.Attributes))
|
||||
for i, attr := range info.Attributes {
|
||||
attrs[i] = RawString(attr)
|
||||
}
|
||||
|
||||
// If the delimiter is NIL, we need to treat it specially by inserting
|
||||
// a nil field (so that it's later converted to an unquoted NIL atom).
|
||||
var del interface{}
|
||||
|
||||
if info.Delimiter != "" {
|
||||
del = info.Delimiter
|
||||
}
|
||||
|
||||
// Thunderbird doesn't understand delimiters if not quoted
|
||||
return []interface{}{attrs, del, FormatMailboxName(name)}
|
||||
}
|
||||
|
||||
// TODO: optimize this
|
||||
func (info *MailboxInfo) match(name, pattern string) bool {
|
||||
i := strings.IndexAny(pattern, "*%")
|
||||
if i == -1 {
|
||||
// No more wildcards
|
||||
return name == pattern
|
||||
}
|
||||
|
||||
// Get parts before and after wildcard
|
||||
chunk, wildcard, rest := pattern[0:i], pattern[i], pattern[i+1:]
|
||||
|
||||
// Check that name begins with chunk
|
||||
if len(chunk) > 0 && !strings.HasPrefix(name, chunk) {
|
||||
return false
|
||||
}
|
||||
name = strings.TrimPrefix(name, chunk)
|
||||
|
||||
// Expand wildcard
|
||||
var j int
|
||||
for j = 0; j < len(name); j++ {
|
||||
if wildcard == '%' && string(name[j]) == info.Delimiter {
|
||||
break // Stop on delimiter if wildcard is %
|
||||
}
|
||||
// Try to match the rest from here
|
||||
if info.match(name[j:], rest) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return info.match(name[j:], rest)
|
||||
}
|
||||
|
||||
// Match checks if a reference and a pattern matches this mailbox name, as
|
||||
// defined in RFC 3501 section 6.3.8.
|
||||
func (info *MailboxInfo) Match(reference, pattern string) bool {
|
||||
name := info.Name
|
||||
|
||||
if info.Delimiter != "" && strings.HasPrefix(pattern, info.Delimiter) {
|
||||
reference = ""
|
||||
pattern = strings.TrimPrefix(pattern, info.Delimiter)
|
||||
}
|
||||
if reference != "" {
|
||||
if info.Delimiter != "" && !strings.HasSuffix(reference, info.Delimiter) {
|
||||
reference += info.Delimiter
|
||||
}
|
||||
if !strings.HasPrefix(name, reference) {
|
||||
return false
|
||||
}
|
||||
name = strings.TrimPrefix(name, reference)
|
||||
}
|
||||
|
||||
return info.match(name, pattern)
|
||||
}
|
||||
|
||||
// A mailbox status.
|
||||
type MailboxStatus struct {
|
||||
// The mailbox name.
|
||||
Name string
|
||||
// True if the mailbox is open in read-only mode.
|
||||
ReadOnly bool
|
||||
// The mailbox items that are currently filled in. This map's values
|
||||
// should not be used directly, they must only be used by libraries
|
||||
// implementing extensions of the IMAP protocol.
|
||||
Items map[StatusItem]interface{}
|
||||
|
||||
// The Items map may be accessed in different goroutines. Protect
|
||||
// concurrent writes.
|
||||
ItemsLocker sync.Mutex
|
||||
|
||||
// The mailbox flags.
|
||||
Flags []string
|
||||
// The mailbox permanent flags.
|
||||
PermanentFlags []string
|
||||
// The sequence number of the first unseen message in the mailbox.
|
||||
UnseenSeqNum uint32
|
||||
|
||||
// The number of messages in this mailbox.
|
||||
Messages uint32
|
||||
// The number of messages not seen since the last time the mailbox was opened.
|
||||
Recent uint32
|
||||
// The number of unread messages.
|
||||
Unseen uint32
|
||||
// The next UID.
|
||||
UidNext uint32
|
||||
// Together with a UID, it is a unique identifier for a message.
|
||||
// Must be greater than or equal to 1.
|
||||
UidValidity uint32
|
||||
|
||||
// Per-mailbox limit of message size. Set only if server supports the
|
||||
// APPENDLIMIT extension.
|
||||
AppendLimit uint32
|
||||
}
|
||||
|
||||
// Create a new mailbox status that will contain the specified items.
|
||||
func NewMailboxStatus(name string, items []StatusItem) *MailboxStatus {
|
||||
status := &MailboxStatus{
|
||||
Name: name,
|
||||
Items: make(map[StatusItem]interface{}),
|
||||
}
|
||||
|
||||
for _, k := range items {
|
||||
status.Items[k] = nil
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func (status *MailboxStatus) Parse(fields []interface{}) error {
|
||||
status.Items = make(map[StatusItem]interface{})
|
||||
|
||||
var k StatusItem
|
||||
for i, f := range fields {
|
||||
if i%2 == 0 {
|
||||
if kstr, ok := f.(string); !ok {
|
||||
return fmt.Errorf("cannot parse mailbox status: key is not a string, but a %T", f)
|
||||
} else {
|
||||
k = StatusItem(strings.ToUpper(kstr))
|
||||
}
|
||||
} else {
|
||||
status.Items[k] = nil
|
||||
|
||||
var err error
|
||||
switch k {
|
||||
case StatusMessages:
|
||||
status.Messages, err = ParseNumber(f)
|
||||
case StatusRecent:
|
||||
status.Recent, err = ParseNumber(f)
|
||||
case StatusUnseen:
|
||||
status.Unseen, err = ParseNumber(f)
|
||||
case StatusUidNext:
|
||||
status.UidNext, err = ParseNumber(f)
|
||||
case StatusUidValidity:
|
||||
status.UidValidity, err = ParseNumber(f)
|
||||
case StatusAppendLimit:
|
||||
status.AppendLimit, err = ParseNumber(f)
|
||||
default:
|
||||
status.Items[k] = f
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (status *MailboxStatus) Format() []interface{} {
|
||||
var fields []interface{}
|
||||
for k, v := range status.Items {
|
||||
switch k {
|
||||
case StatusMessages:
|
||||
v = status.Messages
|
||||
case StatusRecent:
|
||||
v = status.Recent
|
||||
case StatusUnseen:
|
||||
v = status.Unseen
|
||||
case StatusUidNext:
|
||||
v = status.UidNext
|
||||
case StatusUidValidity:
|
||||
v = status.UidValidity
|
||||
case StatusAppendLimit:
|
||||
v = status.AppendLimit
|
||||
}
|
||||
|
||||
fields = append(fields, RawString(k), v)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func FormatMailboxName(name string) interface{} {
|
||||
// Some e-mails servers don't handle quoted INBOX names correctly so we special-case it.
|
||||
if strings.EqualFold(name, "INBOX") {
|
||||
return RawString(name)
|
||||
}
|
||||
return name
|
||||
}
|
1186
vendor/github.com/emersion/go-imap/message.go
generated
vendored
Normal file
1186
vendor/github.com/emersion/go-imap/message.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
467
vendor/github.com/emersion/go-imap/read.go
generated
vendored
Normal file
467
vendor/github.com/emersion/go-imap/read.go
generated
vendored
Normal file
@ -0,0 +1,467 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
sp = ' '
|
||||
cr = '\r'
|
||||
lf = '\n'
|
||||
dquote = '"'
|
||||
literalStart = '{'
|
||||
literalEnd = '}'
|
||||
listStart = '('
|
||||
listEnd = ')'
|
||||
respCodeStart = '['
|
||||
respCodeEnd = ']'
|
||||
)
|
||||
|
||||
const (
|
||||
crlf = "\r\n"
|
||||
nilAtom = "NIL"
|
||||
)
|
||||
|
||||
// TODO: add CTL to atomSpecials
|
||||
var (
|
||||
quotedSpecials = string([]rune{dquote, '\\'})
|
||||
respSpecials = string([]rune{respCodeEnd})
|
||||
atomSpecials = string([]rune{listStart, listEnd, literalStart, sp, '%', '*'}) + quotedSpecials + respSpecials
|
||||
)
|
||||
|
||||
type parseError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func newParseError(text string) error {
|
||||
return &parseError{errors.New(text)}
|
||||
}
|
||||
|
||||
// IsParseError returns true if the provided error is a parse error produced by
|
||||
// Reader.
|
||||
func IsParseError(err error) bool {
|
||||
_, ok := err.(*parseError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// A string reader.
|
||||
type StringReader interface {
|
||||
// ReadString reads until the first occurrence of delim in the input,
|
||||
// returning a string containing the data up to and including the delimiter.
|
||||
// See https://golang.org/pkg/bufio/#Reader.ReadString
|
||||
ReadString(delim byte) (line string, err error)
|
||||
}
|
||||
|
||||
type reader interface {
|
||||
io.Reader
|
||||
io.RuneScanner
|
||||
StringReader
|
||||
}
|
||||
|
||||
// ParseNumber parses a number.
|
||||
func ParseNumber(f interface{}) (uint32, error) {
|
||||
// Useful for tests
|
||||
if n, ok := f.(uint32); ok {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var s string
|
||||
switch f := f.(type) {
|
||||
case RawString:
|
||||
s = string(f)
|
||||
case string:
|
||||
s = f
|
||||
default:
|
||||
return 0, newParseError("expected a number, got a non-atom")
|
||||
}
|
||||
|
||||
nbr, err := strconv.ParseUint(string(s), 10, 32)
|
||||
if err != nil {
|
||||
return 0, &parseError{err}
|
||||
}
|
||||
|
||||
return uint32(nbr), nil
|
||||
}
|
||||
|
||||
// ParseString parses a string, which is either a literal, a quoted string or an
|
||||
// atom.
|
||||
func ParseString(f interface{}) (string, error) {
|
||||
if s, ok := f.(string); ok {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Useful for tests
|
||||
if a, ok := f.(RawString); ok {
|
||||
return string(a), nil
|
||||
}
|
||||
|
||||
if l, ok := f.(Literal); ok {
|
||||
b := make([]byte, l.Len())
|
||||
if _, err := io.ReadFull(l, b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
return "", newParseError("expected a string")
|
||||
}
|
||||
|
||||
// Convert a field list to a string list.
|
||||
func ParseStringList(f interface{}) ([]string, error) {
|
||||
fields, ok := f.([]interface{})
|
||||
if !ok {
|
||||
return nil, newParseError("expected a string list, got a non-list")
|
||||
}
|
||||
|
||||
list := make([]string, len(fields))
|
||||
for i, f := range fields {
|
||||
var err error
|
||||
if list[i], err = ParseString(f); err != nil {
|
||||
return nil, newParseError("cannot parse string in string list: " + err.Error())
|
||||
}
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func trimSuffix(str string, suffix rune) string {
|
||||
return str[:len(str)-1]
|
||||
}
|
||||
|
||||
// An IMAP reader.
|
||||
type Reader struct {
|
||||
MaxLiteralSize uint32 // The maximum literal size.
|
||||
|
||||
reader
|
||||
|
||||
continues chan<- bool
|
||||
|
||||
brackets int
|
||||
inRespCode bool
|
||||
}
|
||||
|
||||
func (r *Reader) ReadSp() error {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if char != sp {
|
||||
return newParseError("expected a space")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadCrlf() (err error) {
|
||||
var char rune
|
||||
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char == lf {
|
||||
return
|
||||
}
|
||||
if char != cr {
|
||||
err = newParseError("line doesn't end with a CR")
|
||||
return
|
||||
}
|
||||
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char != lf {
|
||||
err = newParseError("line doesn't end with a LF")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadAtom() (interface{}, error) {
|
||||
r.brackets = 0
|
||||
|
||||
var atom string
|
||||
for {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: list-wildcards and \
|
||||
if r.brackets == 0 && (char == listStart || char == literalStart || char == dquote) {
|
||||
return nil, newParseError("atom contains forbidden char: " + string(char))
|
||||
}
|
||||
if char == cr || char == lf {
|
||||
break
|
||||
}
|
||||
if r.brackets == 0 && (char == sp || char == listEnd) {
|
||||
break
|
||||
}
|
||||
if char == respCodeEnd {
|
||||
if r.brackets == 0 {
|
||||
if r.inRespCode {
|
||||
break
|
||||
} else {
|
||||
return nil, newParseError("atom contains bad brackets nesting")
|
||||
}
|
||||
}
|
||||
r.brackets--
|
||||
}
|
||||
if char == respCodeStart {
|
||||
r.brackets++
|
||||
}
|
||||
|
||||
atom += string(char)
|
||||
}
|
||||
|
||||
r.UnreadRune()
|
||||
|
||||
if atom == nilAtom {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return atom, nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadLiteral() (Literal, error) {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if char != literalStart {
|
||||
return nil, newParseError("literal string doesn't start with an open brace")
|
||||
}
|
||||
|
||||
lstr, err := r.ReadString(byte(literalEnd))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lstr = trimSuffix(lstr, literalEnd)
|
||||
|
||||
nonSync := strings.HasSuffix(lstr, "+")
|
||||
if nonSync {
|
||||
lstr = trimSuffix(lstr, '+')
|
||||
}
|
||||
|
||||
n, err := strconv.ParseUint(lstr, 10, 32)
|
||||
if err != nil {
|
||||
return nil, newParseError("cannot parse literal length: " + err.Error())
|
||||
}
|
||||
if r.MaxLiteralSize > 0 && uint32(n) > r.MaxLiteralSize {
|
||||
return nil, newParseError("literal exceeding maximum size")
|
||||
}
|
||||
|
||||
if err := r.ReadCrlf(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send continuation request if necessary
|
||||
if r.continues != nil && !nonSync {
|
||||
r.continues <- true
|
||||
}
|
||||
|
||||
// Read literal
|
||||
b := make([]byte, n)
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewBuffer(b), nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadQuotedString() (string, error) {
|
||||
if char, _, err := r.ReadRune(); err != nil {
|
||||
return "", err
|
||||
} else if char != dquote {
|
||||
return "", newParseError("quoted string doesn't start with a double quote")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var escaped bool
|
||||
for {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if char == '\\' && !escaped {
|
||||
escaped = true
|
||||
} else {
|
||||
if char == cr || char == lf {
|
||||
r.UnreadRune()
|
||||
return "", newParseError("CR or LF not allowed in quoted string")
|
||||
}
|
||||
if char == dquote && !escaped {
|
||||
break
|
||||
}
|
||||
|
||||
if !strings.ContainsRune(quotedSpecials, char) && escaped {
|
||||
return "", newParseError("quoted string cannot contain backslash followed by a non-quoted-specials char")
|
||||
}
|
||||
|
||||
buf.WriteRune(char)
|
||||
escaped = false
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadFields() (fields []interface{}, err error) {
|
||||
var char rune
|
||||
for {
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = r.UnreadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var field interface{}
|
||||
ok := true
|
||||
switch char {
|
||||
case literalStart:
|
||||
field, err = r.ReadLiteral()
|
||||
case dquote:
|
||||
field, err = r.ReadQuotedString()
|
||||
case listStart:
|
||||
field, err = r.ReadList()
|
||||
case listEnd:
|
||||
ok = false
|
||||
case cr:
|
||||
return
|
||||
default:
|
||||
field, err = r.ReadAtom()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
fields = append(fields, field)
|
||||
}
|
||||
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char == cr || char == lf || char == listEnd || char == respCodeEnd {
|
||||
if char == cr || char == lf {
|
||||
r.UnreadRune()
|
||||
}
|
||||
return
|
||||
}
|
||||
if char == listStart {
|
||||
r.UnreadRune()
|
||||
continue
|
||||
}
|
||||
if char != sp {
|
||||
err = newParseError("fields are not separated by a space")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) ReadList() (fields []interface{}, err error) {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if char != listStart {
|
||||
err = newParseError("list doesn't start with an open parenthesis")
|
||||
return
|
||||
}
|
||||
|
||||
fields, err = r.ReadFields()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.UnreadRune()
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char != listEnd {
|
||||
err = newParseError("list doesn't end with a close parenthesis")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadLine() (fields []interface{}, err error) {
|
||||
fields, err = r.ReadFields()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.UnreadRune()
|
||||
err = r.ReadCrlf()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadRespCode() (code StatusRespCode, fields []interface{}, err error) {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if char != respCodeStart {
|
||||
err = newParseError("response code doesn't start with an open bracket")
|
||||
return
|
||||
}
|
||||
|
||||
r.inRespCode = true
|
||||
fields, err = r.ReadFields()
|
||||
r.inRespCode = false
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
err = newParseError("response code doesn't contain any field")
|
||||
return
|
||||
}
|
||||
|
||||
codeStr, ok := fields[0].(string)
|
||||
if !ok {
|
||||
err = newParseError("response code doesn't start with a string atom")
|
||||
return
|
||||
}
|
||||
if codeStr == "" {
|
||||
err = newParseError("response code is empty")
|
||||
return
|
||||
}
|
||||
code = StatusRespCode(strings.ToUpper(codeStr))
|
||||
|
||||
fields = fields[1:]
|
||||
|
||||
r.UnreadRune()
|
||||
char, _, err = r.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if char != respCodeEnd {
|
||||
err = newParseError("response code doesn't end with a close bracket")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Reader) ReadInfo() (info string, err error) {
|
||||
info, err = r.ReadString(byte(lf))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info = strings.TrimSuffix(info, string(lf))
|
||||
info = strings.TrimSuffix(info, string(cr))
|
||||
info = strings.TrimLeft(info, " ")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewReader(r reader) *Reader {
|
||||
return &Reader{reader: r}
|
||||
}
|
||||
|
||||
func NewServerReader(r reader, continues chan<- bool) *Reader {
|
||||
return &Reader{reader: r, continues: continues}
|
||||
}
|
||||
|
||||
type Parser interface {
|
||||
Parse(fields []interface{}) error
|
||||
}
|
181
vendor/github.com/emersion/go-imap/response.go
generated
vendored
Normal file
181
vendor/github.com/emersion/go-imap/response.go
generated
vendored
Normal file
@ -0,0 +1,181 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Resp is an IMAP response. It is either a *DataResp, a
|
||||
// *ContinuationReq or a *StatusResp.
|
||||
type Resp interface {
|
||||
resp()
|
||||
}
|
||||
|
||||
// ReadResp reads a single response from a Reader.
|
||||
func ReadResp(r *Reader) (Resp, error) {
|
||||
atom, err := r.ReadAtom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag, ok := atom.(string)
|
||||
if !ok {
|
||||
return nil, newParseError("response tag is not an atom")
|
||||
}
|
||||
|
||||
if tag == "+" {
|
||||
if err := r.ReadSp(); err != nil {
|
||||
r.UnreadRune()
|
||||
}
|
||||
|
||||
resp := &ContinuationReq{}
|
||||
resp.Info, err = r.ReadInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if err := r.ReadSp(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Can be either data or status
|
||||
// Try to parse a status
|
||||
var fields []interface{}
|
||||
if atom, err := r.ReadAtom(); err == nil {
|
||||
fields = append(fields, atom)
|
||||
|
||||
if err := r.ReadSp(); err == nil {
|
||||
if name, ok := atom.(string); ok {
|
||||
status := StatusRespType(name)
|
||||
switch status {
|
||||
case StatusRespOk, StatusRespNo, StatusRespBad, StatusRespPreauth, StatusRespBye:
|
||||
resp := &StatusResp{
|
||||
Tag: tag,
|
||||
Type: status,
|
||||
}
|
||||
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.UnreadRune()
|
||||
|
||||
if char == '[' {
|
||||
// Contains code & arguments
|
||||
resp.Code, resp.Arguments, err = r.ReadRespCode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resp.Info, err = r.ReadInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.UnreadRune()
|
||||
}
|
||||
} else {
|
||||
r.UnreadRune()
|
||||
}
|
||||
|
||||
// Not a status so it's data
|
||||
resp := &DataResp{Tag: tag}
|
||||
|
||||
var remaining []interface{}
|
||||
remaining, err = r.ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.Fields = append(fields, remaining...)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DataResp is an IMAP response containing data.
|
||||
type DataResp struct {
|
||||
// The response tag. Can be either "" for untagged responses, "+" for continuation
|
||||
// requests or a previous command's tag.
|
||||
Tag string
|
||||
// The parsed response fields.
|
||||
Fields []interface{}
|
||||
}
|
||||
|
||||
// NewUntaggedResp creates a new untagged response.
|
||||
func NewUntaggedResp(fields []interface{}) *DataResp {
|
||||
return &DataResp{
|
||||
Tag: "*",
|
||||
Fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *DataResp) resp() {}
|
||||
|
||||
func (r *DataResp) WriteTo(w *Writer) error {
|
||||
tag := RawString(r.Tag)
|
||||
if tag == "" {
|
||||
tag = RawString("*")
|
||||
}
|
||||
|
||||
fields := []interface{}{RawString(tag)}
|
||||
fields = append(fields, r.Fields...)
|
||||
return w.writeLine(fields...)
|
||||
}
|
||||
|
||||
// ContinuationReq is a continuation request response.
|
||||
type ContinuationReq struct {
|
||||
// The info message sent with the continuation request.
|
||||
Info string
|
||||
}
|
||||
|
||||
func (r *ContinuationReq) resp() {}
|
||||
|
||||
func (r *ContinuationReq) WriteTo(w *Writer) error {
|
||||
if err := w.writeString("+"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Info != "" {
|
||||
if err := w.writeString(string(sp) + r.Info); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return w.writeCrlf()
|
||||
}
|
||||
|
||||
// ParseNamedResp attempts to parse a named data response.
|
||||
func ParseNamedResp(resp Resp) (name string, fields []interface{}, ok bool) {
|
||||
data, ok := resp.(*DataResp)
|
||||
if !ok || len(data.Fields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Some responses (namely EXISTS and RECENT) are formatted like so:
|
||||
// [num] [name] [...]
|
||||
// Which is fucking stupid. But we handle that here by checking if the
|
||||
// response name is a number and then rearranging it.
|
||||
if len(data.Fields) > 1 {
|
||||
name, ok := data.Fields[1].(string)
|
||||
if ok {
|
||||
if _, err := ParseNumber(data.Fields[0]); err == nil {
|
||||
fields := []interface{}{data.Fields[0]}
|
||||
fields = append(fields, data.Fields[2:]...)
|
||||
return strings.ToUpper(name), fields, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IMAP commands are formatted like this:
|
||||
// [name] [...]
|
||||
name, ok = data.Fields[0].(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
return strings.ToUpper(name), data.Fields[1:], true
|
||||
}
|
61
vendor/github.com/emersion/go-imap/responses/authenticate.go
generated
vendored
Normal file
61
vendor/github.com/emersion/go-imap/responses/authenticate.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// An AUTHENTICATE response.
|
||||
type Authenticate struct {
|
||||
Mechanism sasl.Client
|
||||
InitialResponse []byte
|
||||
RepliesCh chan []byte
|
||||
}
|
||||
|
||||
// Implements
|
||||
func (r *Authenticate) Replies() <-chan []byte {
|
||||
return r.RepliesCh
|
||||
}
|
||||
|
||||
func (r *Authenticate) writeLine(l string) error {
|
||||
r.RepliesCh <- []byte(l + "\r\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Authenticate) cancel() error {
|
||||
return r.writeLine("*")
|
||||
}
|
||||
|
||||
func (r *Authenticate) Handle(resp imap.Resp) error {
|
||||
cont, ok := resp.(*imap.ContinuationReq)
|
||||
if !ok {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
// Empty challenge, send initial response as stated in RFC 2222 section 5.1
|
||||
if cont.Info == "" && r.InitialResponse != nil {
|
||||
encoded := base64.StdEncoding.EncodeToString(r.InitialResponse)
|
||||
if err := r.writeLine(encoded); err != nil {
|
||||
return err
|
||||
}
|
||||
r.InitialResponse = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
challenge, err := base64.StdEncoding.DecodeString(cont.Info)
|
||||
if err != nil {
|
||||
r.cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
reply, err := r.Mechanism.Next(challenge)
|
||||
if err != nil {
|
||||
r.cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(reply)
|
||||
return r.writeLine(encoded)
|
||||
}
|
20
vendor/github.com/emersion/go-imap/responses/capability.go
generated
vendored
Normal file
20
vendor/github.com/emersion/go-imap/responses/capability.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// A CAPABILITY response.
|
||||
// See RFC 3501 section 7.2.1
|
||||
type Capability struct {
|
||||
Caps []string
|
||||
}
|
||||
|
||||
func (r *Capability) WriteTo(w *imap.Writer) error {
|
||||
fields := []interface{}{imap.RawString("CAPABILITY")}
|
||||
for _, cap := range r.Caps {
|
||||
fields = append(fields, imap.RawString(cap))
|
||||
}
|
||||
|
||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
||||
}
|
33
vendor/github.com/emersion/go-imap/responses/enabled.go
generated
vendored
Normal file
33
vendor/github.com/emersion/go-imap/responses/enabled.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An ENABLED response, defined in RFC 5161 section 3.2.
|
||||
type Enabled struct {
|
||||
Caps []string
|
||||
}
|
||||
|
||||
func (r *Enabled) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != "ENABLED" {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
if caps, err := imap.ParseStringList(fields); err != nil {
|
||||
return err
|
||||
} else {
|
||||
r.Caps = append(r.Caps, caps...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Enabled) WriteTo(w *imap.Writer) error {
|
||||
fields := []interface{}{imap.RawString("ENABLED")}
|
||||
for _, cap := range r.Caps {
|
||||
fields = append(fields, imap.RawString(cap))
|
||||
}
|
||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
||||
}
|
43
vendor/github.com/emersion/go-imap/responses/expunge.go
generated
vendored
Normal file
43
vendor/github.com/emersion/go-imap/responses/expunge.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const expungeName = "EXPUNGE"
|
||||
|
||||
// An EXPUNGE response.
|
||||
// See RFC 3501 section 7.4.1
|
||||
type Expunge struct {
|
||||
SeqNums chan uint32
|
||||
}
|
||||
|
||||
func (r *Expunge) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != expungeName {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
seqNum, err := imap.ParseNumber(fields[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.SeqNums <- seqNum
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Expunge) WriteTo(w *imap.Writer) error {
|
||||
for seqNum := range r.SeqNums {
|
||||
resp := imap.NewUntaggedResp([]interface{}{seqNum, imap.RawString(expungeName)})
|
||||
if err := resp.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
70
vendor/github.com/emersion/go-imap/responses/fetch.go
generated
vendored
Normal file
70
vendor/github.com/emersion/go-imap/responses/fetch.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const fetchName = "FETCH"
|
||||
|
||||
// A FETCH response.
|
||||
// See RFC 3501 section 7.4.2
|
||||
type Fetch struct {
|
||||
Messages chan *imap.Message
|
||||
SeqSet *imap.SeqSet
|
||||
Uid bool
|
||||
}
|
||||
|
||||
func (r *Fetch) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != fetchName {
|
||||
return ErrUnhandled
|
||||
} else if len(fields) < 1 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
seqNum, err := imap.ParseNumber(fields[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgFields, _ := fields[1].([]interface{})
|
||||
msg := &imap.Message{SeqNum: seqNum}
|
||||
if err := msg.Parse(msgFields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Uid && msg.Uid == 0 {
|
||||
// we requested UIDs and got a message without one --> unilateral update --> ignore
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
var num uint32
|
||||
if r.Uid {
|
||||
num = msg.Uid
|
||||
} else {
|
||||
num = seqNum
|
||||
}
|
||||
|
||||
// Check whether we obtained a result we requested with our SeqSet
|
||||
// If the result is not contained in our SeqSet we have to handle an additional special case:
|
||||
// In case we requested UIDs with a dynamic sequence (i.e. * or n:*) and the maximum UID of the mailbox
|
||||
// is less then our n, the server will supply us with the max UID (cf. RFC 3501 §6.4.8 and §9 `seq-range`).
|
||||
// Thus, such a result is correct and has to be returned by us.
|
||||
if !r.SeqSet.Contains(num) && (!r.Uid || !r.SeqSet.Dynamic()) {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
r.Messages <- msg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Fetch) WriteTo(w *imap.Writer) error {
|
||||
var err error
|
||||
for msg := range r.Messages {
|
||||
resp := imap.NewUntaggedResp([]interface{}{msg.SeqNum, imap.RawString(fetchName), msg.Format()})
|
||||
if err == nil {
|
||||
err = resp.WriteTo(w)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
38
vendor/github.com/emersion/go-imap/responses/idle.go
generated
vendored
Normal file
38
vendor/github.com/emersion/go-imap/responses/idle.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An IDLE response.
|
||||
type Idle struct {
|
||||
RepliesCh chan []byte
|
||||
Stop <-chan struct{}
|
||||
|
||||
gotContinuationReq bool
|
||||
}
|
||||
|
||||
func (r *Idle) Replies() <-chan []byte {
|
||||
return r.RepliesCh
|
||||
}
|
||||
|
||||
func (r *Idle) stop() {
|
||||
r.RepliesCh <- []byte("DONE\r\n")
|
||||
}
|
||||
|
||||
func (r *Idle) Handle(resp imap.Resp) error {
|
||||
// Wait for a continuation request
|
||||
if _, ok := resp.(*imap.ContinuationReq); ok && !r.gotContinuationReq {
|
||||
r.gotContinuationReq = true
|
||||
|
||||
// We got a continuation request, wait for r.Stop to be closed
|
||||
go func() {
|
||||
<-r.Stop
|
||||
r.stop()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrUnhandled
|
||||
}
|
57
vendor/github.com/emersion/go-imap/responses/list.go
generated
vendored
Normal file
57
vendor/github.com/emersion/go-imap/responses/list.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const (
|
||||
listName = "LIST"
|
||||
lsubName = "LSUB"
|
||||
)
|
||||
|
||||
// A LIST response.
|
||||
// If Subscribed is set to true, LSUB will be used instead.
|
||||
// See RFC 3501 section 7.2.2
|
||||
type List struct {
|
||||
Mailboxes chan *imap.MailboxInfo
|
||||
Subscribed bool
|
||||
}
|
||||
|
||||
func (r *List) Name() string {
|
||||
if r.Subscribed {
|
||||
return lsubName
|
||||
} else {
|
||||
return listName
|
||||
}
|
||||
}
|
||||
|
||||
func (r *List) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != r.Name() {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
mbox := &imap.MailboxInfo{}
|
||||
if err := mbox.Parse(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Mailboxes <- mbox
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *List) WriteTo(w *imap.Writer) error {
|
||||
respName := r.Name()
|
||||
|
||||
for mbox := range r.Mailboxes {
|
||||
fields := []interface{}{imap.RawString(respName)}
|
||||
fields = append(fields, mbox.Format()...)
|
||||
|
||||
resp := imap.NewUntaggedResp(fields)
|
||||
if err := resp.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
35
vendor/github.com/emersion/go-imap/responses/responses.go
generated
vendored
Normal file
35
vendor/github.com/emersion/go-imap/responses/responses.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// IMAP responses defined in RFC 3501.
|
||||
package responses
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// ErrUnhandled is used when a response hasn't been handled.
|
||||
var ErrUnhandled = errors.New("imap: unhandled response")
|
||||
|
||||
var errNotEnoughFields = errors.New("imap: not enough fields in response")
|
||||
|
||||
// Handler handles responses.
|
||||
type Handler interface {
|
||||
// Handle processes a response. If the response cannot be processed,
|
||||
// ErrUnhandledResp must be returned.
|
||||
Handle(resp imap.Resp) error
|
||||
}
|
||||
|
||||
// HandlerFunc is a function that handles responses.
|
||||
type HandlerFunc func(resp imap.Resp) error
|
||||
|
||||
// Handle implements Handler.
|
||||
func (f HandlerFunc) Handle(resp imap.Resp) error {
|
||||
return f(resp)
|
||||
}
|
||||
|
||||
// Replier is a Handler that needs to send raw data (for instance
|
||||
// AUTHENTICATE).
|
||||
type Replier interface {
|
||||
Handler
|
||||
Replies() <-chan []byte
|
||||
}
|
41
vendor/github.com/emersion/go-imap/responses/search.go
generated
vendored
Normal file
41
vendor/github.com/emersion/go-imap/responses/search.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const searchName = "SEARCH"
|
||||
|
||||
// A SEARCH response.
|
||||
// See RFC 3501 section 7.2.5
|
||||
type Search struct {
|
||||
Ids []uint32
|
||||
}
|
||||
|
||||
func (r *Search) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != searchName {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
r.Ids = make([]uint32, len(fields))
|
||||
for i, f := range fields {
|
||||
if id, err := imap.ParseNumber(f); err != nil {
|
||||
return err
|
||||
} else {
|
||||
r.Ids[i] = id
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Search) WriteTo(w *imap.Writer) (err error) {
|
||||
fields := []interface{}{imap.RawString(searchName)}
|
||||
for _, id := range r.Ids {
|
||||
fields = append(fields, id)
|
||||
}
|
||||
|
||||
resp := imap.NewUntaggedResp(fields)
|
||||
return resp.WriteTo(w)
|
||||
}
|
142
vendor/github.com/emersion/go-imap/responses/select.go
generated
vendored
Normal file
142
vendor/github.com/emersion/go-imap/responses/select.go
generated
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// A SELECT response.
|
||||
type Select struct {
|
||||
Mailbox *imap.MailboxStatus
|
||||
}
|
||||
|
||||
func (r *Select) Handle(resp imap.Resp) error {
|
||||
if r.Mailbox == nil {
|
||||
r.Mailbox = &imap.MailboxStatus{Items: make(map[imap.StatusItem]interface{})}
|
||||
}
|
||||
mbox := r.Mailbox
|
||||
|
||||
switch resp := resp.(type) {
|
||||
case *imap.DataResp:
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != "FLAGS" {
|
||||
return ErrUnhandled
|
||||
} else if len(fields) < 1 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
flags, _ := fields[0].([]interface{})
|
||||
mbox.Flags, _ = imap.ParseStringList(flags)
|
||||
case *imap.StatusResp:
|
||||
if len(resp.Arguments) < 1 {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
var item imap.StatusItem
|
||||
switch resp.Code {
|
||||
case "UNSEEN":
|
||||
mbox.UnseenSeqNum, _ = imap.ParseNumber(resp.Arguments[0])
|
||||
case "PERMANENTFLAGS":
|
||||
flags, _ := resp.Arguments[0].([]interface{})
|
||||
mbox.PermanentFlags, _ = imap.ParseStringList(flags)
|
||||
case "UIDNEXT":
|
||||
mbox.UidNext, _ = imap.ParseNumber(resp.Arguments[0])
|
||||
item = imap.StatusUidNext
|
||||
case "UIDVALIDITY":
|
||||
mbox.UidValidity, _ = imap.ParseNumber(resp.Arguments[0])
|
||||
item = imap.StatusUidValidity
|
||||
default:
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
if item != "" {
|
||||
mbox.ItemsLocker.Lock()
|
||||
mbox.Items[item] = nil
|
||||
mbox.ItemsLocker.Unlock()
|
||||
}
|
||||
default:
|
||||
return ErrUnhandled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Select) WriteTo(w *imap.Writer) error {
|
||||
mbox := r.Mailbox
|
||||
|
||||
if mbox.Flags != nil {
|
||||
flags := make([]interface{}, len(mbox.Flags))
|
||||
for i, f := range mbox.Flags {
|
||||
flags[i] = imap.RawString(f)
|
||||
}
|
||||
res := imap.NewUntaggedResp([]interface{}{imap.RawString("FLAGS"), flags})
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mbox.PermanentFlags != nil {
|
||||
flags := make([]interface{}, len(mbox.PermanentFlags))
|
||||
for i, f := range mbox.PermanentFlags {
|
||||
flags[i] = imap.RawString(f)
|
||||
}
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodePermanentFlags,
|
||||
Arguments: []interface{}{flags},
|
||||
Info: "Flags permitted.",
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mbox.UnseenSeqNum > 0 {
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodeUnseen,
|
||||
Arguments: []interface{}{mbox.UnseenSeqNum},
|
||||
Info: fmt.Sprintf("Message %d is first unseen", mbox.UnseenSeqNum),
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for k := range r.Mailbox.Items {
|
||||
switch k {
|
||||
case imap.StatusMessages:
|
||||
res := imap.NewUntaggedResp([]interface{}{mbox.Messages, imap.RawString("EXISTS")})
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
case imap.StatusRecent:
|
||||
res := imap.NewUntaggedResp([]interface{}{mbox.Recent, imap.RawString("RECENT")})
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
case imap.StatusUidNext:
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodeUidNext,
|
||||
Arguments: []interface{}{mbox.UidNext},
|
||||
Info: "Predicted next UID",
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
case imap.StatusUidValidity:
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodeUidValidity,
|
||||
Arguments: []interface{}{mbox.UidValidity},
|
||||
Info: "UIDs valid",
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
53
vendor/github.com/emersion/go-imap/responses/status.go
generated
vendored
Normal file
53
vendor/github.com/emersion/go-imap/responses/status.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
const statusName = "STATUS"
|
||||
|
||||
// A STATUS response.
|
||||
// See RFC 3501 section 7.2.4
|
||||
type Status struct {
|
||||
Mailbox *imap.MailboxStatus
|
||||
}
|
||||
|
||||
func (r *Status) Handle(resp imap.Resp) error {
|
||||
if r.Mailbox == nil {
|
||||
r.Mailbox = &imap.MailboxStatus{}
|
||||
}
|
||||
mbox := r.Mailbox
|
||||
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != statusName {
|
||||
return ErrUnhandled
|
||||
} else if len(fields) < 2 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
if name, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
|
||||
return err
|
||||
} else {
|
||||
mbox.Name = imap.CanonicalMailboxName(name)
|
||||
}
|
||||
|
||||
var items []interface{}
|
||||
if items, ok = fields[1].([]interface{}); !ok {
|
||||
return errors.New("STATUS response expects a list as second argument")
|
||||
}
|
||||
|
||||
mbox.Items = nil
|
||||
return mbox.Parse(items)
|
||||
}
|
||||
|
||||
func (r *Status) WriteTo(w *imap.Writer) error {
|
||||
mbox := r.Mailbox
|
||||
name, _ := utf7.Encoding.NewEncoder().String(mbox.Name)
|
||||
fields := []interface{}{imap.RawString(statusName), imap.FormatMailboxName(name), mbox.Format()}
|
||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
||||
}
|
371
vendor/github.com/emersion/go-imap/search.go
generated
vendored
Normal file
371
vendor/github.com/emersion/go-imap/search.go
generated
vendored
Normal file
@ -0,0 +1,371 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func maybeString(mystery interface{}) string {
|
||||
if s, ok := mystery.(string); ok {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func convertField(f interface{}, charsetReader func(io.Reader) io.Reader) string {
|
||||
// An IMAP string contains only 7-bit data, no need to decode it
|
||||
if s, ok := f.(string); ok {
|
||||
return s
|
||||
}
|
||||
|
||||
// If no charset is provided, getting directly the string is faster
|
||||
if charsetReader == nil {
|
||||
if stringer, ok := f.(fmt.Stringer); ok {
|
||||
return stringer.String()
|
||||
}
|
||||
}
|
||||
|
||||
// Not a string, it must be a literal
|
||||
l, ok := f.(Literal)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
var r io.Reader = l
|
||||
if charsetReader != nil {
|
||||
if dec := charsetReader(r); dec != nil {
|
||||
r = dec
|
||||
}
|
||||
}
|
||||
|
||||
b := make([]byte, l.Len())
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func popSearchField(fields []interface{}) (interface{}, []interface{}, error) {
|
||||
if len(fields) == 0 {
|
||||
return nil, nil, errors.New("imap: no enough fields for search key")
|
||||
}
|
||||
return fields[0], fields[1:], nil
|
||||
}
|
||||
|
||||
// SearchCriteria is a search criteria. A message matches the criteria if and
|
||||
// only if it matches each one of its fields.
|
||||
type SearchCriteria struct {
|
||||
SeqNum *SeqSet // Sequence number is in sequence set
|
||||
Uid *SeqSet // UID is in sequence set
|
||||
|
||||
// Time and timezone are ignored
|
||||
Since time.Time // Internal date is since this date
|
||||
Before time.Time // Internal date is before this date
|
||||
SentSince time.Time // Date header field is since this date
|
||||
SentBefore time.Time // Date header field is before this date
|
||||
|
||||
Header textproto.MIMEHeader // Each header field value is present
|
||||
Body []string // Each string is in the body
|
||||
Text []string // Each string is in the text (header + body)
|
||||
|
||||
WithFlags []string // Each flag is present
|
||||
WithoutFlags []string // Each flag is not present
|
||||
|
||||
Larger uint32 // Size is larger than this number
|
||||
Smaller uint32 // Size is smaller than this number
|
||||
|
||||
Not []*SearchCriteria // Each criteria doesn't match
|
||||
Or [][2]*SearchCriteria // Each criteria pair has at least one match of two
|
||||
}
|
||||
|
||||
// NewSearchCriteria creates a new search criteria.
|
||||
func NewSearchCriteria() *SearchCriteria {
|
||||
return &SearchCriteria{Header: make(textproto.MIMEHeader)}
|
||||
}
|
||||
|
||||
func (c *SearchCriteria) parseField(fields []interface{}, charsetReader func(io.Reader) io.Reader) ([]interface{}, error) {
|
||||
if len(fields) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
f := fields[0]
|
||||
fields = fields[1:]
|
||||
|
||||
if subfields, ok := f.([]interface{}); ok {
|
||||
return fields, c.ParseWithCharset(subfields, charsetReader)
|
||||
}
|
||||
|
||||
key, ok := f.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("imap: invalid search criteria field type: %T", f)
|
||||
}
|
||||
key = strings.ToUpper(key)
|
||||
|
||||
var err error
|
||||
switch key {
|
||||
case "ALL":
|
||||
// Nothing to do
|
||||
case "ANSWERED", "DELETED", "DRAFT", "FLAGGED", "RECENT", "SEEN":
|
||||
c.WithFlags = append(c.WithFlags, CanonicalFlag("\\"+key))
|
||||
case "BCC", "CC", "FROM", "SUBJECT", "TO":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.Header == nil {
|
||||
c.Header = make(textproto.MIMEHeader)
|
||||
}
|
||||
c.Header.Add(key, convertField(f, charsetReader))
|
||||
case "BEFORE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.Before.IsZero() || t.Before(c.Before) {
|
||||
c.Before = t
|
||||
}
|
||||
case "BODY":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.Body = append(c.Body, convertField(f, charsetReader))
|
||||
}
|
||||
case "HEADER":
|
||||
var f1, f2 interface{}
|
||||
if f1, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if f2, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if c.Header == nil {
|
||||
c.Header = make(textproto.MIMEHeader)
|
||||
}
|
||||
c.Header.Add(maybeString(f1), convertField(f2, charsetReader))
|
||||
}
|
||||
case "KEYWORD":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.WithFlags = append(c.WithFlags, CanonicalFlag(maybeString(f)))
|
||||
}
|
||||
case "LARGER":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if n, err := ParseNumber(f); err != nil {
|
||||
return nil, err
|
||||
} else if c.Larger == 0 || n > c.Larger {
|
||||
c.Larger = n
|
||||
}
|
||||
case "NEW":
|
||||
c.WithFlags = append(c.WithFlags, RecentFlag)
|
||||
c.WithoutFlags = append(c.WithoutFlags, SeenFlag)
|
||||
case "NOT":
|
||||
not := new(SearchCriteria)
|
||||
if fields, err = not.parseField(fields, charsetReader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Not = append(c.Not, not)
|
||||
case "OLD":
|
||||
c.WithoutFlags = append(c.WithoutFlags, RecentFlag)
|
||||
case "ON":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.Since = t
|
||||
c.Before = t.Add(24 * time.Hour)
|
||||
}
|
||||
case "OR":
|
||||
c1, c2 := new(SearchCriteria), new(SearchCriteria)
|
||||
if fields, err = c1.parseField(fields, charsetReader); err != nil {
|
||||
return nil, err
|
||||
} else if fields, err = c2.parseField(fields, charsetReader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Or = append(c.Or, [2]*SearchCriteria{c1, c2})
|
||||
case "SENTBEFORE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.SentBefore.IsZero() || t.Before(c.SentBefore) {
|
||||
c.SentBefore = t
|
||||
}
|
||||
case "SENTON":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.SentSince = t
|
||||
c.SentBefore = t.Add(24 * time.Hour)
|
||||
}
|
||||
case "SENTSINCE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.SentSince.IsZero() || t.After(c.SentSince) {
|
||||
c.SentSince = t
|
||||
}
|
||||
case "SINCE":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
} else if c.Since.IsZero() || t.After(c.Since) {
|
||||
c.Since = t
|
||||
}
|
||||
case "SMALLER":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if n, err := ParseNumber(f); err != nil {
|
||||
return nil, err
|
||||
} else if c.Smaller == 0 || n < c.Smaller {
|
||||
c.Smaller = n
|
||||
}
|
||||
case "TEXT":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.Text = append(c.Text, convertField(f, charsetReader))
|
||||
}
|
||||
case "UID":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else if c.Uid, err = ParseSeqSet(maybeString(f)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "UNANSWERED", "UNDELETED", "UNDRAFT", "UNFLAGGED", "UNSEEN":
|
||||
unflag := strings.TrimPrefix(key, "UN")
|
||||
c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag("\\"+unflag))
|
||||
case "UNKEYWORD":
|
||||
if f, fields, err = popSearchField(fields); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag(maybeString(f)))
|
||||
}
|
||||
default: // Try to parse a sequence set
|
||||
if c.SeqNum, err = ParseSeqSet(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// ParseWithCharset parses a search criteria from the provided fields.
|
||||
// charsetReader is an optional function that converts from the fields charset
|
||||
// to UTF-8.
|
||||
func (c *SearchCriteria) ParseWithCharset(fields []interface{}, charsetReader func(io.Reader) io.Reader) error {
|
||||
for len(fields) > 0 {
|
||||
var err error
|
||||
if fields, err = c.parseField(fields, charsetReader); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format formats search criteria to fields. UTF-8 is used.
|
||||
func (c *SearchCriteria) Format() []interface{} {
|
||||
var fields []interface{}
|
||||
|
||||
if c.SeqNum != nil {
|
||||
fields = append(fields, c.SeqNum)
|
||||
}
|
||||
if c.Uid != nil {
|
||||
fields = append(fields, RawString("UID"), c.Uid)
|
||||
}
|
||||
|
||||
if !c.Since.IsZero() && !c.Before.IsZero() && c.Before.Sub(c.Since) == 24*time.Hour {
|
||||
fields = append(fields, RawString("ON"), searchDate(c.Since))
|
||||
} else {
|
||||
if !c.Since.IsZero() {
|
||||
fields = append(fields, RawString("SINCE"), searchDate(c.Since))
|
||||
}
|
||||
if !c.Before.IsZero() {
|
||||
fields = append(fields, RawString("BEFORE"), searchDate(c.Before))
|
||||
}
|
||||
}
|
||||
if !c.SentSince.IsZero() && !c.SentBefore.IsZero() && c.SentBefore.Sub(c.SentSince) == 24*time.Hour {
|
||||
fields = append(fields, RawString("SENTON"), searchDate(c.SentSince))
|
||||
} else {
|
||||
if !c.SentSince.IsZero() {
|
||||
fields = append(fields, RawString("SENTSINCE"), searchDate(c.SentSince))
|
||||
}
|
||||
if !c.SentBefore.IsZero() {
|
||||
fields = append(fields, RawString("SENTBEFORE"), searchDate(c.SentBefore))
|
||||
}
|
||||
}
|
||||
|
||||
for key, values := range c.Header {
|
||||
var prefields []interface{}
|
||||
switch key {
|
||||
case "Bcc", "Cc", "From", "Subject", "To":
|
||||
prefields = []interface{}{RawString(strings.ToUpper(key))}
|
||||
default:
|
||||
prefields = []interface{}{RawString("HEADER"), key}
|
||||
}
|
||||
for _, value := range values {
|
||||
fields = append(fields, prefields...)
|
||||
fields = append(fields, value)
|
||||
}
|
||||
}
|
||||
|
||||
for _, value := range c.Body {
|
||||
fields = append(fields, RawString("BODY"), value)
|
||||
}
|
||||
for _, value := range c.Text {
|
||||
fields = append(fields, RawString("TEXT"), value)
|
||||
}
|
||||
|
||||
for _, flag := range c.WithFlags {
|
||||
var subfields []interface{}
|
||||
switch flag {
|
||||
case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, RecentFlag, SeenFlag:
|
||||
subfields = []interface{}{RawString(strings.ToUpper(strings.TrimPrefix(flag, "\\")))}
|
||||
default:
|
||||
subfields = []interface{}{RawString("KEYWORD"), RawString(flag)}
|
||||
}
|
||||
fields = append(fields, subfields...)
|
||||
}
|
||||
for _, flag := range c.WithoutFlags {
|
||||
var subfields []interface{}
|
||||
switch flag {
|
||||
case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, SeenFlag:
|
||||
subfields = []interface{}{RawString("UN" + strings.ToUpper(strings.TrimPrefix(flag, "\\")))}
|
||||
case RecentFlag:
|
||||
subfields = []interface{}{RawString("OLD")}
|
||||
default:
|
||||
subfields = []interface{}{RawString("UNKEYWORD"), RawString(flag)}
|
||||
}
|
||||
fields = append(fields, subfields...)
|
||||
}
|
||||
|
||||
if c.Larger > 0 {
|
||||
fields = append(fields, RawString("LARGER"), c.Larger)
|
||||
}
|
||||
if c.Smaller > 0 {
|
||||
fields = append(fields, RawString("SMALLER"), c.Smaller)
|
||||
}
|
||||
|
||||
for _, not := range c.Not {
|
||||
fields = append(fields, RawString("NOT"), not.Format())
|
||||
}
|
||||
|
||||
for _, or := range c.Or {
|
||||
fields = append(fields, RawString("OR"), or[0].Format(), or[1].Format())
|
||||
}
|
||||
|
||||
// Not a single criteria given, add ALL criteria as fallback
|
||||
if len(fields) == 0 {
|
||||
fields = append(fields, RawString("ALL"))
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
289
vendor/github.com/emersion/go-imap/seqset.go
generated
vendored
Normal file
289
vendor/github.com/emersion/go-imap/seqset.go
generated
vendored
Normal file
@ -0,0 +1,289 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrBadSeqSet is used to report problems with the format of a sequence set
|
||||
// value.
|
||||
type ErrBadSeqSet string
|
||||
|
||||
func (err ErrBadSeqSet) Error() string {
|
||||
return fmt.Sprintf("imap: bad sequence set value %q", string(err))
|
||||
}
|
||||
|
||||
// Seq represents a single seq-number or seq-range value (RFC 3501 ABNF). Values
|
||||
// may be static (e.g. "1", "2:4") or dynamic (e.g. "*", "1:*"). A seq-number is
|
||||
// represented by setting Start = Stop. Zero is used to represent "*", which is
|
||||
// safe because seq-number uses nz-number rule. The order of values is always
|
||||
// Start <= Stop, except when representing "n:*", where Start = n and Stop = 0.
|
||||
type Seq struct {
|
||||
Start, Stop uint32
|
||||
}
|
||||
|
||||
// parseSeqNumber parses a single seq-number value (non-zero uint32 or "*").
|
||||
func parseSeqNumber(v string) (uint32, error) {
|
||||
if n, err := strconv.ParseUint(v, 10, 32); err == nil && v[0] != '0' {
|
||||
return uint32(n), nil
|
||||
} else if v == "*" {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, ErrBadSeqSet(v)
|
||||
}
|
||||
|
||||
// parseSeq creates a new seq instance by parsing strings in the format "n" or
|
||||
// "n:m", where n and/or m may be "*". An error is returned for invalid values.
|
||||
func parseSeq(v string) (s Seq, err error) {
|
||||
if sep := strings.IndexRune(v, ':'); sep < 0 {
|
||||
s.Start, err = parseSeqNumber(v)
|
||||
s.Stop = s.Start
|
||||
return
|
||||
} else if s.Start, err = parseSeqNumber(v[:sep]); err == nil {
|
||||
if s.Stop, err = parseSeqNumber(v[sep+1:]); err == nil {
|
||||
if (s.Stop < s.Start && s.Stop != 0) || s.Start == 0 {
|
||||
s.Start, s.Stop = s.Stop, s.Start
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return s, ErrBadSeqSet(v)
|
||||
}
|
||||
|
||||
// Contains returns true if the seq-number q is contained in sequence value s.
|
||||
// The dynamic value "*" contains only other "*" values, the dynamic range "n:*"
|
||||
// contains "*" and all numbers >= n.
|
||||
func (s Seq) Contains(q uint32) bool {
|
||||
if q == 0 {
|
||||
return s.Stop == 0 // "*" is contained only in "*" and "n:*"
|
||||
}
|
||||
return s.Start != 0 && s.Start <= q && (q <= s.Stop || s.Stop == 0)
|
||||
}
|
||||
|
||||
// Less returns true if s precedes and does not contain seq-number q.
|
||||
func (s Seq) Less(q uint32) bool {
|
||||
return (s.Stop < q || q == 0) && s.Stop != 0
|
||||
}
|
||||
|
||||
// Merge combines sequence values s and t into a single union if the two
|
||||
// intersect or one is a superset of the other. The order of s and t does not
|
||||
// matter. If the values cannot be merged, s is returned unmodified and ok is
|
||||
// set to false.
|
||||
func (s Seq) Merge(t Seq) (union Seq, ok bool) {
|
||||
if union = s; s == t {
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
if s.Start != 0 && t.Start != 0 {
|
||||
// s and t are any combination of "n", "n:m", or "n:*"
|
||||
if s.Start > t.Start {
|
||||
s, t = t, s
|
||||
}
|
||||
// s starts at or before t, check where it ends
|
||||
if (s.Stop >= t.Stop && t.Stop != 0) || s.Stop == 0 {
|
||||
return s, true // s is a superset of t
|
||||
}
|
||||
// s is "n" or "n:m", if m == ^uint32(0) then t is "n:*"
|
||||
if s.Stop+1 >= t.Start || s.Stop == ^uint32(0) {
|
||||
return Seq{s.Start, t.Stop}, true // s intersects or touches t
|
||||
}
|
||||
return
|
||||
}
|
||||
// exactly one of s and t is "*"
|
||||
if s.Start == 0 {
|
||||
if t.Stop == 0 {
|
||||
return t, true // s is "*", t is "n:*"
|
||||
}
|
||||
} else if s.Stop == 0 {
|
||||
return s, true // s is "n:*", t is "*"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String returns sequence value s as a seq-number or seq-range string.
|
||||
func (s Seq) String() string {
|
||||
if s.Start == s.Stop {
|
||||
if s.Start == 0 {
|
||||
return "*"
|
||||
}
|
||||
return strconv.FormatUint(uint64(s.Start), 10)
|
||||
}
|
||||
b := strconv.AppendUint(make([]byte, 0, 24), uint64(s.Start), 10)
|
||||
if s.Stop == 0 {
|
||||
return string(append(b, ':', '*'))
|
||||
}
|
||||
return string(strconv.AppendUint(append(b, ':'), uint64(s.Stop), 10))
|
||||
}
|
||||
|
||||
// SeqSet is used to represent a set of message sequence numbers or UIDs (see
|
||||
// sequence-set ABNF rule). The zero value is an empty set.
|
||||
type SeqSet struct {
|
||||
Set []Seq
|
||||
}
|
||||
|
||||
// ParseSeqSet returns a new SeqSet instance after parsing the set string.
|
||||
func ParseSeqSet(set string) (s *SeqSet, err error) {
|
||||
s = new(SeqSet)
|
||||
return s, s.Add(set)
|
||||
}
|
||||
|
||||
// Add inserts new sequence values into the set. The string format is described
|
||||
// by RFC 3501 sequence-set ABNF rule. If an error is encountered, all values
|
||||
// inserted successfully prior to the error remain in the set.
|
||||
func (s *SeqSet) Add(set string) error {
|
||||
for _, sv := range strings.Split(set, ",") {
|
||||
v, err := parseSeq(sv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.insert(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNum inserts new sequence numbers into the set. The value 0 represents "*".
|
||||
func (s *SeqSet) AddNum(q ...uint32) {
|
||||
for _, v := range q {
|
||||
s.insert(Seq{v, v})
|
||||
}
|
||||
}
|
||||
|
||||
// AddRange inserts a new sequence range into the set.
|
||||
func (s *SeqSet) AddRange(Start, Stop uint32) {
|
||||
if (Stop < Start && Stop != 0) || Start == 0 {
|
||||
s.insert(Seq{Stop, Start})
|
||||
} else {
|
||||
s.insert(Seq{Start, Stop})
|
||||
}
|
||||
}
|
||||
|
||||
// AddSet inserts all values from t into s.
|
||||
func (s *SeqSet) AddSet(t *SeqSet) {
|
||||
for _, v := range t.Set {
|
||||
s.insert(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all values from the set.
|
||||
func (s *SeqSet) Clear() {
|
||||
s.Set = s.Set[:0]
|
||||
}
|
||||
|
||||
// Empty returns true if the sequence set does not contain any values.
|
||||
func (s SeqSet) Empty() bool {
|
||||
return len(s.Set) == 0
|
||||
}
|
||||
|
||||
// Dynamic returns true if the set contains "*" or "n:*" values.
|
||||
func (s SeqSet) Dynamic() bool {
|
||||
return len(s.Set) > 0 && s.Set[len(s.Set)-1].Stop == 0
|
||||
}
|
||||
|
||||
// Contains returns true if the non-zero sequence number or UID q is contained
|
||||
// in the set. The dynamic range "n:*" contains all q >= n. It is the caller's
|
||||
// responsibility to handle the special case where q is the maximum UID in the
|
||||
// mailbox and q < n (i.e. the set cannot match UIDs against "*:n" or "*" since
|
||||
// it doesn't know what the maximum value is).
|
||||
func (s SeqSet) Contains(q uint32) bool {
|
||||
if _, ok := s.search(q); ok {
|
||||
return q != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String returns a sorted representation of all contained sequence values.
|
||||
func (s SeqSet) String() string {
|
||||
if len(s.Set) == 0 {
|
||||
return ""
|
||||
}
|
||||
b := make([]byte, 0, 64)
|
||||
for _, v := range s.Set {
|
||||
b = append(b, ',')
|
||||
if v.Start == 0 {
|
||||
b = append(b, '*')
|
||||
continue
|
||||
}
|
||||
b = strconv.AppendUint(b, uint64(v.Start), 10)
|
||||
if v.Start != v.Stop {
|
||||
if v.Stop == 0 {
|
||||
b = append(b, ':', '*')
|
||||
continue
|
||||
}
|
||||
b = strconv.AppendUint(append(b, ':'), uint64(v.Stop), 10)
|
||||
}
|
||||
}
|
||||
return string(b[1:])
|
||||
}
|
||||
|
||||
// insert adds sequence value v to the set.
|
||||
func (s *SeqSet) insert(v Seq) {
|
||||
i, _ := s.search(v.Start)
|
||||
merged := false
|
||||
if i > 0 {
|
||||
// try merging with the preceding entry (e.g. "1,4".insert(2), i == 1)
|
||||
s.Set[i-1], merged = s.Set[i-1].Merge(v)
|
||||
}
|
||||
if i == len(s.Set) {
|
||||
// v was either merged with the last entry or needs to be appended
|
||||
if !merged {
|
||||
s.insertAt(i, v)
|
||||
}
|
||||
return
|
||||
} else if merged {
|
||||
i--
|
||||
} else if s.Set[i], merged = s.Set[i].Merge(v); !merged {
|
||||
s.insertAt(i, v) // insert in the middle (e.g. "1,5".insert(3), i == 1)
|
||||
return
|
||||
}
|
||||
// v was merged with s.Set[i], continue trying to merge until the end
|
||||
for j := i + 1; j < len(s.Set); j++ {
|
||||
if s.Set[i], merged = s.Set[i].Merge(s.Set[j]); !merged {
|
||||
if j > i+1 {
|
||||
// cut out all entries between i and j that were merged
|
||||
s.Set = append(s.Set[:i+1], s.Set[j:]...)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// everything after s.Set[i] was merged
|
||||
s.Set = s.Set[:i+1]
|
||||
}
|
||||
|
||||
// insertAt inserts a new sequence value v at index i, resizing s.Set as needed.
|
||||
func (s *SeqSet) insertAt(i int, v Seq) {
|
||||
if n := len(s.Set); i == n {
|
||||
// insert at the end
|
||||
s.Set = append(s.Set, v)
|
||||
return
|
||||
} else if n < cap(s.Set) {
|
||||
// enough space, shift everything at and after i to the right
|
||||
s.Set = s.Set[:n+1]
|
||||
copy(s.Set[i+1:], s.Set[i:])
|
||||
} else {
|
||||
// allocate new slice and copy everything, n is at least 1
|
||||
set := make([]Seq, n+1, n*2)
|
||||
copy(set, s.Set[:i])
|
||||
copy(set[i+1:], s.Set[i:])
|
||||
s.Set = set
|
||||
}
|
||||
s.Set[i] = v
|
||||
}
|
||||
|
||||
// search attempts to find the index of the sequence set value that contains q.
|
||||
// If no values contain q, the returned index is the position where q should be
|
||||
// inserted and ok is set to false.
|
||||
func (s SeqSet) search(q uint32) (i int, ok bool) {
|
||||
min, max := 0, len(s.Set)-1
|
||||
for min < max {
|
||||
if mid := (min + max) >> 1; s.Set[mid].Less(q) {
|
||||
min = mid + 1
|
||||
} else {
|
||||
max = mid
|
||||
}
|
||||
}
|
||||
if max < 0 || s.Set[min].Less(q) {
|
||||
return len(s.Set), false // q is the new largest value
|
||||
}
|
||||
return min, s.Set[min].Contains(q)
|
||||
}
|
136
vendor/github.com/emersion/go-imap/status.go
generated
vendored
Normal file
136
vendor/github.com/emersion/go-imap/status.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// A status response type.
|
||||
type StatusRespType string
|
||||
|
||||
// Status response types defined in RFC 3501 section 7.1.
|
||||
const (
|
||||
// The OK response indicates an information message from the server. When
|
||||
// tagged, it indicates successful completion of the associated command.
|
||||
// The untagged form indicates an information-only message.
|
||||
StatusRespOk StatusRespType = "OK"
|
||||
|
||||
// The NO response indicates an operational error message from the
|
||||
// server. When tagged, it indicates unsuccessful completion of the
|
||||
// associated command. The untagged form indicates a warning; the
|
||||
// command can still complete successfully.
|
||||
StatusRespNo StatusRespType = "NO"
|
||||
|
||||
// The BAD response indicates an error message from the server. When
|
||||
// tagged, it reports a protocol-level error in the client's command;
|
||||
// the tag indicates the command that caused the error. The untagged
|
||||
// form indicates a protocol-level error for which the associated
|
||||
// command can not be determined; it can also indicate an internal
|
||||
// server failure.
|
||||
StatusRespBad StatusRespType = "BAD"
|
||||
|
||||
// The PREAUTH response is always untagged, and is one of three
|
||||
// possible greetings at connection startup. It indicates that the
|
||||
// connection has already been authenticated by external means; thus
|
||||
// no LOGIN command is needed.
|
||||
StatusRespPreauth StatusRespType = "PREAUTH"
|
||||
|
||||
// The BYE response is always untagged, and indicates that the server
|
||||
// is about to close the connection.
|
||||
StatusRespBye StatusRespType = "BYE"
|
||||
)
|
||||
|
||||
type StatusRespCode string
|
||||
|
||||
// Status response codes defined in RFC 3501 section 7.1.
|
||||
const (
|
||||
CodeAlert StatusRespCode = "ALERT"
|
||||
CodeBadCharset StatusRespCode = "BADCHARSET"
|
||||
CodeCapability StatusRespCode = "CAPABILITY"
|
||||
CodeParse StatusRespCode = "PARSE"
|
||||
CodePermanentFlags StatusRespCode = "PERMANENTFLAGS"
|
||||
CodeReadOnly StatusRespCode = "READ-ONLY"
|
||||
CodeReadWrite StatusRespCode = "READ-WRITE"
|
||||
CodeTryCreate StatusRespCode = "TRYCREATE"
|
||||
CodeUidNext StatusRespCode = "UIDNEXT"
|
||||
CodeUidValidity StatusRespCode = "UIDVALIDITY"
|
||||
CodeUnseen StatusRespCode = "UNSEEN"
|
||||
)
|
||||
|
||||
// A status response.
|
||||
// See RFC 3501 section 7.1
|
||||
type StatusResp struct {
|
||||
// The response tag. If empty, it defaults to *.
|
||||
Tag string
|
||||
// The status type.
|
||||
Type StatusRespType
|
||||
// The status code.
|
||||
// See https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml
|
||||
Code StatusRespCode
|
||||
// Arguments provided with the status code.
|
||||
Arguments []interface{}
|
||||
// The status info.
|
||||
Info string
|
||||
}
|
||||
|
||||
func (r *StatusResp) resp() {}
|
||||
|
||||
// If this status is NO or BAD, returns an error with the status info.
|
||||
// Otherwise, returns nil.
|
||||
func (r *StatusResp) Err() error {
|
||||
if r == nil {
|
||||
// No status response, connection closed before we get one
|
||||
return errors.New("imap: connection closed during command execution")
|
||||
}
|
||||
|
||||
if r.Type == StatusRespNo || r.Type == StatusRespBad {
|
||||
return errors.New(r.Info)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *StatusResp) WriteTo(w *Writer) error {
|
||||
tag := RawString(r.Tag)
|
||||
if tag == "" {
|
||||
tag = "*"
|
||||
}
|
||||
|
||||
if err := w.writeFields([]interface{}{RawString(tag), RawString(r.Type)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeString(string(sp)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Code != "" {
|
||||
if err := w.writeRespCode(r.Code, r.Arguments); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeString(string(sp)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.writeString(r.Info); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeCrlf()
|
||||
}
|
||||
|
||||
// ErrStatusResp can be returned by a server.Handler to replace the default status
|
||||
// response. The response tag must be empty.
|
||||
//
|
||||
// To suppress default response, Resp should be set to nil.
|
||||
type ErrStatusResp struct {
|
||||
// Response to send instead of default.
|
||||
Resp *StatusResp
|
||||
}
|
||||
|
||||
func (err *ErrStatusResp) Error() string {
|
||||
if err.Resp == nil {
|
||||
return "imap: suppressed response"
|
||||
}
|
||||
return err.Resp.Info
|
||||
}
|
149
vendor/github.com/emersion/go-imap/utf7/decoder.go
generated
vendored
Normal file
149
vendor/github.com/emersion/go-imap/utf7/decoder.go
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
// ErrInvalidUTF7 means that a transformer encountered invalid UTF-7.
|
||||
var ErrInvalidUTF7 = errors.New("utf7: invalid UTF-7")
|
||||
|
||||
type decoder struct {
|
||||
ascii bool
|
||||
}
|
||||
|
||||
func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
for i := 0; i < len(src); i++ {
|
||||
ch := src[i]
|
||||
|
||||
if ch < min || ch > max { // Illegal code point in ASCII mode
|
||||
err = ErrInvalidUTF7
|
||||
return
|
||||
}
|
||||
|
||||
if ch != '&' {
|
||||
if nDst+1 > len(dst) {
|
||||
err = transform.ErrShortDst
|
||||
return
|
||||
}
|
||||
|
||||
nSrc++
|
||||
|
||||
dst[nDst] = ch
|
||||
nDst++
|
||||
|
||||
d.ascii = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the end of the Base64 or "&-" segment
|
||||
start := i + 1
|
||||
for i++; i < len(src) && src[i] != '-'; i++ {
|
||||
if src[i] == '\r' || src[i] == '\n' { // base64 package ignores CR and LF
|
||||
err = ErrInvalidUTF7
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if i == len(src) { // Implicit shift ("&...")
|
||||
if atEOF {
|
||||
err = ErrInvalidUTF7
|
||||
} else {
|
||||
err = transform.ErrShortSrc
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var b []byte
|
||||
if i == start { // Escape sequence "&-"
|
||||
b = []byte{'&'}
|
||||
d.ascii = true
|
||||
} else { // Control or non-ASCII code points in base64
|
||||
if !d.ascii { // Null shift ("&...-&...-")
|
||||
err = ErrInvalidUTF7
|
||||
return
|
||||
}
|
||||
|
||||
b = decode(src[start:i])
|
||||
d.ascii = false
|
||||
}
|
||||
|
||||
if len(b) == 0 { // Bad encoding
|
||||
err = ErrInvalidUTF7
|
||||
return
|
||||
}
|
||||
|
||||
if nDst+len(b) > len(dst) {
|
||||
d.ascii = true
|
||||
err = transform.ErrShortDst
|
||||
return
|
||||
}
|
||||
|
||||
nSrc = i + 1
|
||||
|
||||
for _, ch := range b {
|
||||
dst[nDst] = ch
|
||||
nDst++
|
||||
}
|
||||
}
|
||||
|
||||
if atEOF {
|
||||
d.ascii = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *decoder) Reset() {
|
||||
d.ascii = true
|
||||
}
|
||||
|
||||
// Extracts UTF-16-BE bytes from base64 data and converts them to UTF-8.
|
||||
// A nil slice is returned if the encoding is invalid.
|
||||
func decode(b64 []byte) []byte {
|
||||
var b []byte
|
||||
|
||||
// Allocate a single block of memory large enough to store the Base64 data
|
||||
// (if padding is required), UTF-16-BE bytes, and decoded UTF-8 bytes.
|
||||
// Since a 2-byte UTF-16 sequence may expand into a 3-byte UTF-8 sequence,
|
||||
// double the space allocation for UTF-8.
|
||||
if n := len(b64); b64[n-1] == '=' {
|
||||
return nil
|
||||
} else if n&3 == 0 {
|
||||
b = make([]byte, b64Enc.DecodedLen(n)*3)
|
||||
} else {
|
||||
n += 4 - n&3
|
||||
b = make([]byte, n+b64Enc.DecodedLen(n)*3)
|
||||
copy(b[copy(b, b64):n], []byte("=="))
|
||||
b64, b = b[:n], b[n:]
|
||||
}
|
||||
|
||||
// Decode Base64 into the first 1/3rd of b
|
||||
n, err := b64Enc.Decode(b, b64)
|
||||
if err != nil || n&1 == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode UTF-16-BE into the remaining 2/3rds of b
|
||||
b, s := b[:n], b[n:]
|
||||
j := 0
|
||||
for i := 0; i < n; i += 2 {
|
||||
r := rune(b[i])<<8 | rune(b[i+1])
|
||||
if utf16.IsSurrogate(r) {
|
||||
if i += 2; i == n {
|
||||
return nil
|
||||
}
|
||||
r2 := rune(b[i])<<8 | rune(b[i+1])
|
||||
if r = utf16.DecodeRune(r, r2); r == repl {
|
||||
return nil
|
||||
}
|
||||
} else if min <= r && r <= max {
|
||||
return nil
|
||||
}
|
||||
j += utf8.EncodeRune(s[j:], r)
|
||||
}
|
||||
return s[:j]
|
||||
}
|
91
vendor/github.com/emersion/go-imap/utf7/encoder.go
generated
vendored
Normal file
91
vendor/github.com/emersion/go-imap/utf7/encoder.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
type encoder struct{}
|
||||
|
||||
func (e *encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
for i := 0; i < len(src); {
|
||||
ch := src[i]
|
||||
|
||||
var b []byte
|
||||
if min <= ch && ch <= max {
|
||||
b = []byte{ch}
|
||||
if ch == '&' {
|
||||
b = append(b, '-')
|
||||
}
|
||||
|
||||
i++
|
||||
} else {
|
||||
start := i
|
||||
|
||||
// Find the next printable ASCII code point
|
||||
i++
|
||||
for i < len(src) && (src[i] < min || src[i] > max) {
|
||||
i++
|
||||
}
|
||||
|
||||
if !atEOF && i == len(src) {
|
||||
err = transform.ErrShortSrc
|
||||
return
|
||||
}
|
||||
|
||||
b = encode(src[start:i])
|
||||
}
|
||||
|
||||
if nDst+len(b) > len(dst) {
|
||||
err = transform.ErrShortDst
|
||||
return
|
||||
}
|
||||
|
||||
nSrc = i
|
||||
|
||||
for _, ch := range b {
|
||||
dst[nDst] = ch
|
||||
nDst++
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e *encoder) Reset() {}
|
||||
|
||||
// Converts string s from UTF-8 to UTF-16-BE, encodes the result as base64,
|
||||
// removes the padding, and adds UTF-7 shifts.
|
||||
func encode(s []byte) []byte {
|
||||
// len(s) is sufficient for UTF-8 to UTF-16 conversion if there are no
|
||||
// control code points (see table below).
|
||||
b := make([]byte, 0, len(s)+4)
|
||||
for len(s) > 0 {
|
||||
r, size := utf8.DecodeRune(s)
|
||||
if r > utf8.MaxRune {
|
||||
r, size = utf8.RuneError, 1 // Bug fix (issue 3785)
|
||||
}
|
||||
s = s[size:]
|
||||
if r1, r2 := utf16.EncodeRune(r); r1 != repl {
|
||||
b = append(b, byte(r1>>8), byte(r1))
|
||||
r = r2
|
||||
}
|
||||
b = append(b, byte(r>>8), byte(r))
|
||||
}
|
||||
|
||||
// Encode as base64
|
||||
n := b64Enc.EncodedLen(len(b)) + 2
|
||||
b64 := make([]byte, n)
|
||||
b64Enc.Encode(b64[1:], b)
|
||||
|
||||
// Strip padding
|
||||
n -= 2 - (len(b)+2)%3
|
||||
b64 = b64[:n]
|
||||
|
||||
// Add UTF-7 shifts
|
||||
b64[0] = '&'
|
||||
b64[n-1] = '-'
|
||||
return b64
|
||||
}
|
34
vendor/github.com/emersion/go-imap/utf7/utf7.go
generated
vendored
Normal file
34
vendor/github.com/emersion/go-imap/utf7/utf7.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// Package utf7 implements modified UTF-7 encoding defined in RFC 3501 section 5.1.3
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
)
|
||||
|
||||
const (
|
||||
min = 0x20 // Minimum self-representing UTF-7 value
|
||||
max = 0x7E // Maximum self-representing UTF-7 value
|
||||
|
||||
repl = '\uFFFD' // Unicode replacement code point
|
||||
)
|
||||
|
||||
var b64Enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,")
|
||||
|
||||
type enc struct{}
|
||||
|
||||
func (e enc) NewDecoder() *encoding.Decoder {
|
||||
return &encoding.Decoder{
|
||||
Transformer: &decoder{true},
|
||||
}
|
||||
}
|
||||
|
||||
func (e enc) NewEncoder() *encoding.Encoder {
|
||||
return &encoding.Encoder{
|
||||
Transformer: &encoder{},
|
||||
}
|
||||
}
|
||||
|
||||
// Encoding is the modified UTF-7 encoding.
|
||||
var Encoding encoding.Encoding = enc{}
|
255
vendor/github.com/emersion/go-imap/write.go
generated
vendored
Normal file
255
vendor/github.com/emersion/go-imap/write.go
generated
vendored
Normal file
@ -0,0 +1,255 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type flusher interface {
|
||||
Flush() error
|
||||
}
|
||||
|
||||
type (
|
||||
// A raw string.
|
||||
RawString string
|
||||
)
|
||||
|
||||
type WriterTo interface {
|
||||
WriteTo(w *Writer) error
|
||||
}
|
||||
|
||||
func formatNumber(num uint32) string {
|
||||
return strconv.FormatUint(uint64(num), 10)
|
||||
}
|
||||
|
||||
// Convert a string list to a field list.
|
||||
func FormatStringList(list []string) (fields []interface{}) {
|
||||
fields = make([]interface{}, len(list))
|
||||
for i, v := range list {
|
||||
fields[i] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check if a string is 8-bit clean.
|
||||
func isAscii(s string) bool {
|
||||
for _, c := range s {
|
||||
if c > unicode.MaxASCII || unicode.IsControl(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// An IMAP writer.
|
||||
type Writer struct {
|
||||
io.Writer
|
||||
|
||||
AllowAsyncLiterals bool
|
||||
|
||||
continues <-chan bool
|
||||
}
|
||||
|
||||
// Helper function to write a string to w.
|
||||
func (w *Writer) writeString(s string) error {
|
||||
_, err := io.WriteString(w.Writer, s)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *Writer) writeCrlf() error {
|
||||
if err := w.writeString(crlf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func (w *Writer) writeNumber(num uint32) error {
|
||||
return w.writeString(formatNumber(num))
|
||||
}
|
||||
|
||||
func (w *Writer) writeQuoted(s string) error {
|
||||
return w.writeString(strconv.Quote(s))
|
||||
}
|
||||
|
||||
func (w *Writer) writeQuotedOrLiteral(s string) error {
|
||||
if !isAscii(s) {
|
||||
// IMAP doesn't allow 8-bit data outside literals
|
||||
return w.writeLiteral(bytes.NewBufferString(s))
|
||||
}
|
||||
|
||||
return w.writeQuoted(s)
|
||||
}
|
||||
|
||||
func (w *Writer) writeDateTime(t time.Time, layout string) error {
|
||||
if t.IsZero() {
|
||||
return w.writeString(nilAtom)
|
||||
}
|
||||
return w.writeQuoted(t.Format(layout))
|
||||
}
|
||||
|
||||
func (w *Writer) writeFields(fields []interface{}) error {
|
||||
for i, field := range fields {
|
||||
if i > 0 { // Write separator
|
||||
if err := w.writeString(string(sp)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.writeField(field); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) writeList(fields []interface{}) error {
|
||||
if err := w.writeString(string(listStart)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeFields(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeString(string(listEnd))
|
||||
}
|
||||
|
||||
// LiteralLengthErr is returned when the Len() of the Literal object does not
|
||||
// match the actual length of the byte stream.
|
||||
type LiteralLengthErr struct {
|
||||
Actual int
|
||||
Expected int
|
||||
}
|
||||
|
||||
func (e LiteralLengthErr) Error() string {
|
||||
return fmt.Sprintf("imap: size of Literal is not equal to Len() (%d != %d)", e.Expected, e.Actual)
|
||||
}
|
||||
|
||||
func (w *Writer) writeLiteral(l Literal) error {
|
||||
if l == nil {
|
||||
return w.writeString(nilAtom)
|
||||
}
|
||||
|
||||
unsyncLiteral := w.AllowAsyncLiterals && l.Len() <= 4096
|
||||
|
||||
header := string(literalStart) + strconv.Itoa(l.Len())
|
||||
if unsyncLiteral {
|
||||
header += string('+')
|
||||
}
|
||||
header += string(literalEnd) + crlf
|
||||
if err := w.writeString(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If a channel is available, wait for a continuation request before sending data
|
||||
if !unsyncLiteral && w.continues != nil {
|
||||
// Make sure to flush the writer, otherwise we may never receive a continuation request
|
||||
if err := w.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !<-w.continues {
|
||||
return fmt.Errorf("imap: cannot send literal: no continuation request received")
|
||||
}
|
||||
}
|
||||
|
||||
// In case of bufio.Buffer, it will be 0 after io.Copy.
|
||||
literalLen := int64(l.Len())
|
||||
|
||||
n, err := io.CopyN(w, l, literalLen)
|
||||
if err != nil {
|
||||
if err == io.EOF && n != literalLen {
|
||||
return LiteralLengthErr{int(n), l.Len()}
|
||||
}
|
||||
return err
|
||||
}
|
||||
extra, _ := io.Copy(ioutil.Discard, l)
|
||||
if extra != 0 {
|
||||
return LiteralLengthErr{int(n + extra), l.Len()}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) writeField(field interface{}) error {
|
||||
if field == nil {
|
||||
return w.writeString(nilAtom)
|
||||
}
|
||||
|
||||
switch field := field.(type) {
|
||||
case RawString:
|
||||
return w.writeString(string(field))
|
||||
case string:
|
||||
return w.writeQuotedOrLiteral(field)
|
||||
case int:
|
||||
return w.writeNumber(uint32(field))
|
||||
case uint32:
|
||||
return w.writeNumber(field)
|
||||
case Literal:
|
||||
return w.writeLiteral(field)
|
||||
case []interface{}:
|
||||
return w.writeList(field)
|
||||
case envelopeDateTime:
|
||||
return w.writeDateTime(time.Time(field), envelopeDateTimeLayout)
|
||||
case searchDate:
|
||||
return w.writeDateTime(time.Time(field), searchDateLayout)
|
||||
case Date:
|
||||
return w.writeDateTime(time.Time(field), DateLayout)
|
||||
case DateTime:
|
||||
return w.writeDateTime(time.Time(field), DateTimeLayout)
|
||||
case time.Time:
|
||||
return w.writeDateTime(field, DateTimeLayout)
|
||||
case *SeqSet:
|
||||
return w.writeString(field.String())
|
||||
case *BodySectionName:
|
||||
// Can contain spaces - that's why we don't just pass it as a string
|
||||
return w.writeString(string(field.FetchItem()))
|
||||
}
|
||||
|
||||
return fmt.Errorf("imap: cannot format field: %v", field)
|
||||
}
|
||||
|
||||
func (w *Writer) writeRespCode(code StatusRespCode, args []interface{}) error {
|
||||
if err := w.writeString(string(respCodeStart)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := []interface{}{RawString(code)}
|
||||
fields = append(fields, args...)
|
||||
|
||||
if err := w.writeFields(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeString(string(respCodeEnd))
|
||||
}
|
||||
|
||||
func (w *Writer) writeLine(fields ...interface{}) error {
|
||||
if err := w.writeFields(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeCrlf()
|
||||
}
|
||||
|
||||
func (w *Writer) Flush() error {
|
||||
if f, ok := w.Writer.(flusher); ok {
|
||||
return f.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{Writer: w}
|
||||
}
|
||||
|
||||
func NewClientWriter(w io.Writer, continues <-chan bool) *Writer {
|
||||
return &Writer{Writer: w, continues: continues}
|
||||
}
|
19
vendor/github.com/emersion/go-sasl/.build.yml
generated
vendored
Normal file
19
vendor/github.com/emersion/go-sasl/.build.yml
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
image: alpine/latest
|
||||
packages:
|
||||
- go
|
||||
# Required by codecov
|
||||
- bash
|
||||
- findutils
|
||||
sources:
|
||||
- https://github.com/emersion/go-sasl
|
||||
tasks:
|
||||
- build: |
|
||||
cd go-sasl
|
||||
go build -v ./...
|
||||
- test: |
|
||||
cd go-sasl
|
||||
go test -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
- upload-coverage: |
|
||||
cd go-sasl
|
||||
export CODECOV_TOKEN=3f257f71-a128-4834-8f68-2b534e9f4cb1
|
||||
curl -s https://codecov.io/bash | bash
|
24
vendor/github.com/emersion/go-sasl/.gitignore
generated
vendored
Normal file
24
vendor/github.com/emersion/go-sasl/.gitignore
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
21
vendor/github.com/emersion/go-sasl/LICENSE
generated
vendored
Normal file
21
vendor/github.com/emersion/go-sasl/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 emersion
|
||||
|
||||
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.
|
17
vendor/github.com/emersion/go-sasl/README.md
generated
vendored
Normal file
17
vendor/github.com/emersion/go-sasl/README.md
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# go-sasl
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/emersion/go-sasl?status.svg)](https://godoc.org/github.com/emersion/go-sasl)
|
||||
[![Build Status](https://travis-ci.org/emersion/go-sasl.svg?branch=master)](https://travis-ci.org/emersion/go-sasl)
|
||||
|
||||
A [SASL](https://tools.ietf.org/html/rfc4422) library written in Go.
|
||||
|
||||
Implemented mechanisms:
|
||||
* [ANONYMOUS](https://tools.ietf.org/html/rfc4505)
|
||||
* [EXTERNAL](https://tools.ietf.org/html/rfc4422#appendix-A)
|
||||
* [LOGIN](https://tools.ietf.org/html/draft-murchison-sasl-login-00) (obsolete, use PLAIN instead)
|
||||
* [PLAIN](https://tools.ietf.org/html/rfc4616)
|
||||
* [OAUTHBEARER](https://tools.ietf.org/html/rfc7628)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
56
vendor/github.com/emersion/go-sasl/anonymous.go
generated
vendored
Normal file
56
vendor/github.com/emersion/go-sasl/anonymous.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
package sasl
|
||||
|
||||
// The ANONYMOUS mechanism name.
|
||||
const Anonymous = "ANONYMOUS"
|
||||
|
||||
type anonymousClient struct {
|
||||
Trace string
|
||||
}
|
||||
|
||||
func (c *anonymousClient) Start() (mech string, ir []byte, err error) {
|
||||
mech = Anonymous
|
||||
ir = []byte(c.Trace)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *anonymousClient) Next(challenge []byte) (response []byte, err error) {
|
||||
return nil, ErrUnexpectedServerChallenge
|
||||
}
|
||||
|
||||
// A client implementation of the ANONYMOUS authentication mechanism, as
|
||||
// described in RFC 4505.
|
||||
func NewAnonymousClient(trace string) Client {
|
||||
return &anonymousClient{trace}
|
||||
}
|
||||
|
||||
// Get trace information from clients logging in anonymously.
|
||||
type AnonymousAuthenticator func(trace string) error
|
||||
|
||||
type anonymousServer struct {
|
||||
done bool
|
||||
authenticate AnonymousAuthenticator
|
||||
}
|
||||
|
||||
func (s *anonymousServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||
if s.done {
|
||||
err = ErrUnexpectedClientResponse
|
||||
return
|
||||
}
|
||||
|
||||
// No initial response, send an empty challenge
|
||||
if response == nil {
|
||||
return []byte{}, false, nil
|
||||
}
|
||||
|
||||
s.done = true
|
||||
|
||||
err = s.authenticate(string(response))
|
||||
done = true
|
||||
return
|
||||
}
|
||||
|
||||
// A server implementation of the ANONYMOUS authentication mechanism, as
|
||||
// described in RFC 4505.
|
||||
func NewAnonymousServer(authenticator AnonymousAuthenticator) Server {
|
||||
return &anonymousServer{authenticate: authenticator}
|
||||
}
|
26
vendor/github.com/emersion/go-sasl/external.go
generated
vendored
Normal file
26
vendor/github.com/emersion/go-sasl/external.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
package sasl
|
||||
|
||||
// The EXTERNAL mechanism name.
|
||||
const External = "EXTERNAL"
|
||||
|
||||
type externalClient struct {
|
||||
Identity string
|
||||
}
|
||||
|
||||
func (a *externalClient) Start() (mech string, ir []byte, err error) {
|
||||
mech = External
|
||||
ir = []byte(a.Identity)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *externalClient) Next(challenge []byte) (response []byte, err error) {
|
||||
return nil, ErrUnexpectedServerChallenge
|
||||
}
|
||||
|
||||
// An implementation of the EXTERNAL authentication mechanism, as described in
|
||||
// RFC 4422. Authorization identity may be left blank to indicate that the
|
||||
// client is requesting to act as the identity associated with the
|
||||
// authentication credentials.
|
||||
func NewExternalClient(identity string) Client {
|
||||
return &externalClient{identity}
|
||||
}
|
89
vendor/github.com/emersion/go-sasl/login.go
generated
vendored
Normal file
89
vendor/github.com/emersion/go-sasl/login.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// The LOGIN mechanism name.
|
||||
const Login = "LOGIN"
|
||||
|
||||
var expectedChallenge = []byte("Password:")
|
||||
|
||||
type loginClient struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (a *loginClient) Start() (mech string, ir []byte, err error) {
|
||||
mech = "LOGIN"
|
||||
ir = []byte(a.Username)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *loginClient) Next(challenge []byte) (response []byte, err error) {
|
||||
if bytes.Compare(challenge, expectedChallenge) != 0 {
|
||||
return nil, ErrUnexpectedServerChallenge
|
||||
} else {
|
||||
return []byte(a.Password), nil
|
||||
}
|
||||
}
|
||||
|
||||
// A client implementation of the LOGIN authentication mechanism for SMTP,
|
||||
// as described in http://www.iana.org/go/draft-murchison-sasl-login
|
||||
//
|
||||
// It is considered obsolete, and should not be used when other mechanisms are
|
||||
// available. For plaintext password authentication use PLAIN mechanism.
|
||||
func NewLoginClient(username, password string) Client {
|
||||
return &loginClient{username, password}
|
||||
}
|
||||
|
||||
// Authenticates users with an username and a password.
|
||||
type LoginAuthenticator func(username, password string) error
|
||||
|
||||
type loginState int
|
||||
|
||||
const (
|
||||
loginNotStarted loginState = iota
|
||||
loginWaitingUsername
|
||||
loginWaitingPassword
|
||||
)
|
||||
|
||||
type loginServer struct {
|
||||
state loginState
|
||||
username, password string
|
||||
authenticate LoginAuthenticator
|
||||
}
|
||||
|
||||
// A server implementation of the LOGIN authentication mechanism, as described
|
||||
// in https://tools.ietf.org/html/draft-murchison-sasl-login-00.
|
||||
//
|
||||
// LOGIN is obsolete and should only be enabled for legacy clients that cannot
|
||||
// be updated to use PLAIN.
|
||||
func NewLoginServer(authenticator LoginAuthenticator) Server {
|
||||
return &loginServer{authenticate: authenticator}
|
||||
}
|
||||
|
||||
func (a *loginServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||
switch a.state {
|
||||
case loginNotStarted:
|
||||
// Check for initial response field, as per RFC4422 section 3
|
||||
if response == nil {
|
||||
challenge = []byte("Username:")
|
||||
break
|
||||
}
|
||||
a.state++
|
||||
fallthrough
|
||||
case loginWaitingUsername:
|
||||
a.username = string(response)
|
||||
challenge = []byte("Password:")
|
||||
case loginWaitingPassword:
|
||||
a.password = string(response)
|
||||
err = a.authenticate(a.username, a.password)
|
||||
done = true
|
||||
default:
|
||||
err = ErrUnexpectedClientResponse
|
||||
}
|
||||
|
||||
a.state++
|
||||
return
|
||||
}
|
191
vendor/github.com/emersion/go-sasl/oauthbearer.go
generated
vendored
Normal file
191
vendor/github.com/emersion/go-sasl/oauthbearer.go
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The OAUTHBEARER mechanism name.
|
||||
const OAuthBearer = "OAUTHBEARER"
|
||||
|
||||
type OAuthBearerError struct {
|
||||
Status string `json:"status"`
|
||||
Schemes string `json:"schemes"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
type OAuthBearerOptions struct {
|
||||
Username string
|
||||
Token string
|
||||
Host string
|
||||
Port int
|
||||
}
|
||||
|
||||
// Implements error
|
||||
func (err *OAuthBearerError) Error() string {
|
||||
return fmt.Sprintf("OAUTHBEARER authentication error (%v)", err.Status)
|
||||
}
|
||||
|
||||
type oauthBearerClient struct {
|
||||
OAuthBearerOptions
|
||||
}
|
||||
|
||||
func (a *oauthBearerClient) Start() (mech string, ir []byte, err error) {
|
||||
mech = OAuthBearer
|
||||
var str = "n,a=" + a.Username + ","
|
||||
|
||||
if a.Host != "" {
|
||||
str += "\x01host=" + a.Host
|
||||
}
|
||||
|
||||
if a.Port != 0 {
|
||||
str += "\x01port=" + strconv.Itoa(a.Port)
|
||||
}
|
||||
str += "\x01auth=Bearer " + a.Token + "\x01\x01"
|
||||
ir = []byte(str)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *oauthBearerClient) Next(challenge []byte) ([]byte, error) {
|
||||
authBearerErr := &OAuthBearerError{}
|
||||
if err := json.Unmarshal(challenge, authBearerErr); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return nil, authBearerErr
|
||||
}
|
||||
}
|
||||
|
||||
// An implementation of the OAUTHBEARER authentication mechanism, as
|
||||
// described in RFC 7628.
|
||||
func NewOAuthBearerClient(opt *OAuthBearerOptions) Client {
|
||||
return &oauthBearerClient{*opt}
|
||||
}
|
||||
|
||||
type OAuthBearerAuthenticator func(opts OAuthBearerOptions) *OAuthBearerError
|
||||
|
||||
type oauthBearerServer struct {
|
||||
done bool
|
||||
failErr error
|
||||
authenticate OAuthBearerAuthenticator
|
||||
}
|
||||
|
||||
func (a *oauthBearerServer) fail(descr string) ([]byte, bool, error) {
|
||||
blob, err := json.Marshal(OAuthBearerError{
|
||||
Status: "invalid_request",
|
||||
Schemes: "bearer",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err) // wtf
|
||||
}
|
||||
a.failErr = errors.New(descr)
|
||||
return blob, false, nil
|
||||
}
|
||||
|
||||
func (a *oauthBearerServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||
// Per RFC, we cannot just send an error, we need to return JSON-structured
|
||||
// value as a challenge and then after getting dummy response from the
|
||||
// client stop the exchange.
|
||||
if a.failErr != nil {
|
||||
// Server libraries (go-smtp, go-imap) will not call Next on
|
||||
// protocol-specific SASL cancel response ('*'). However, GS2 (and
|
||||
// indirectly OAUTHBEARER) defines a protocol-independent way to do so
|
||||
// using 0x01.
|
||||
if len(response) != 1 && response[0] != 0x01 {
|
||||
return nil, true, errors.New("unexpected response")
|
||||
}
|
||||
return nil, true, a.failErr
|
||||
}
|
||||
|
||||
if a.done {
|
||||
err = ErrUnexpectedClientResponse
|
||||
return
|
||||
}
|
||||
|
||||
// Generate empty challenge.
|
||||
if response == nil {
|
||||
return []byte{}, false, nil
|
||||
}
|
||||
|
||||
a.done = true
|
||||
|
||||
// Cut n,a=username,\x01host=...\x01auth=...
|
||||
// into
|
||||
// n
|
||||
// a=username
|
||||
// \x01host=...\x01auth=...\x01\x01
|
||||
parts := bytes.SplitN(response, []byte{','}, 3)
|
||||
if len(parts) != 3 {
|
||||
return a.fail("Invalid response")
|
||||
}
|
||||
if !bytes.Equal(parts[0], []byte{'n'}) {
|
||||
return a.fail("Invalid response, missing 'n'")
|
||||
}
|
||||
opts := OAuthBearerOptions{}
|
||||
if !bytes.HasPrefix(parts[1], []byte("a=")) {
|
||||
return a.fail("Invalid response, missing 'a'")
|
||||
}
|
||||
opts.Username = string(bytes.TrimPrefix(parts[1], []byte("a=")))
|
||||
|
||||
// Cut \x01host=...\x01auth=...\x01\x01
|
||||
// into
|
||||
// *empty*
|
||||
// host=...
|
||||
// auth=...
|
||||
// *empty*
|
||||
//
|
||||
// Note that this code does not do a lot of checks to make sure the input
|
||||
// follows the exact format specified by RFC.
|
||||
params := bytes.Split(parts[2], []byte{0x01})
|
||||
for _, p := range params {
|
||||
// Skip empty fields (one at start and end).
|
||||
if len(p) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
pParts := bytes.SplitN(p, []byte{'='}, 2)
|
||||
if len(pParts) != 2 {
|
||||
return a.fail("Invalid response, missing '='")
|
||||
}
|
||||
|
||||
switch string(pParts[0]) {
|
||||
case "host":
|
||||
opts.Host = string(pParts[1])
|
||||
case "port":
|
||||
port, err := strconv.ParseUint(string(pParts[1]), 10, 16)
|
||||
if err != nil {
|
||||
return a.fail("Invalid response, malformed 'port' value")
|
||||
}
|
||||
opts.Port = int(port)
|
||||
case "auth":
|
||||
const prefix = "bearer "
|
||||
strValue := string(pParts[1])
|
||||
// Token type is case-insensitive.
|
||||
if !strings.HasPrefix(strings.ToLower(strValue), prefix) {
|
||||
return a.fail("Unsupported token type")
|
||||
}
|
||||
opts.Token = strValue[len(prefix):]
|
||||
default:
|
||||
return a.fail("Invalid response, unknown parameter: " + string(pParts[0]))
|
||||
}
|
||||
}
|
||||
|
||||
authzErr := a.authenticate(opts)
|
||||
if authzErr != nil {
|
||||
blob, err := json.Marshal(authzErr)
|
||||
if err != nil {
|
||||
panic(err) // wtf
|
||||
}
|
||||
a.failErr = authzErr
|
||||
return blob, false, nil
|
||||
}
|
||||
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
func NewOAuthBearerServer(auth OAuthBearerAuthenticator) Server {
|
||||
return &oauthBearerServer{authenticate: auth}
|
||||
}
|
77
vendor/github.com/emersion/go-sasl/plain.go
generated
vendored
Normal file
77
vendor/github.com/emersion/go-sasl/plain.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
package sasl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// The PLAIN mechanism name.
|
||||
const Plain = "PLAIN"
|
||||
|
||||
type plainClient struct {
|
||||
Identity string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (a *plainClient) Start() (mech string, ir []byte, err error) {
|
||||
mech = "PLAIN"
|
||||
ir = []byte(a.Identity + "\x00" + a.Username + "\x00" + a.Password)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *plainClient) Next(challenge []byte) (response []byte, err error) {
|
||||
return nil, ErrUnexpectedServerChallenge
|
||||
}
|
||||
|
||||
// A client implementation of the PLAIN authentication mechanism, as described
|
||||
// in RFC 4616. Authorization identity may be left blank to indicate that it is
|
||||
// the same as the username.
|
||||
func NewPlainClient(identity, username, password string) Client {
|
||||
return &plainClient{identity, username, password}
|
||||
}
|
||||
|
||||
// Authenticates users with an identity, a username and a password. If the
|
||||
// identity is left blank, it indicates that it is the same as the username.
|
||||
// If identity is not empty and the server doesn't support it, an error must be
|
||||
// returned.
|
||||
type PlainAuthenticator func(identity, username, password string) error
|
||||
|
||||
type plainServer struct {
|
||||
done bool
|
||||
authenticate PlainAuthenticator
|
||||
}
|
||||
|
||||
func (a *plainServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||
if a.done {
|
||||
err = ErrUnexpectedClientResponse
|
||||
return
|
||||
}
|
||||
|
||||
// No initial response, send an empty challenge
|
||||
if response == nil {
|
||||
return []byte{}, false, nil
|
||||
}
|
||||
|
||||
a.done = true
|
||||
|
||||
parts := bytes.Split(response, []byte("\x00"))
|
||||
if len(parts) != 3 {
|
||||
err = errors.New("Invalid response")
|
||||
return
|
||||
}
|
||||
|
||||
identity := string(parts[0])
|
||||
username := string(parts[1])
|
||||
password := string(parts[2])
|
||||
|
||||
err = a.authenticate(identity, username, password)
|
||||
done = true
|
||||
return
|
||||
}
|
||||
|
||||
// A server implementation of the PLAIN authentication mechanism, as described
|
||||
// in RFC 4616.
|
||||
func NewPlainServer(authenticator PlainAuthenticator) Server {
|
||||
return &plainServer{authenticate: authenticator}
|
||||
}
|
45
vendor/github.com/emersion/go-sasl/sasl.go
generated
vendored
Normal file
45
vendor/github.com/emersion/go-sasl/sasl.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
// Library for Simple Authentication and Security Layer (SASL) defined in RFC 4422.
|
||||
package sasl
|
||||
|
||||
// Note:
|
||||
// Most of this code was copied, with some modifications, from net/smtp. It
|
||||
// would be better if Go provided a standard package (e.g. crypto/sasl) that
|
||||
// could be shared by SMTP, IMAP, and other packages.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Common SASL errors.
|
||||
var (
|
||||
ErrUnexpectedClientResponse = errors.New("sasl: unexpected client response")
|
||||
ErrUnexpectedServerChallenge = errors.New("sasl: unexpected server challenge")
|
||||
)
|
||||
|
||||
// Client interface to perform challenge-response authentication.
|
||||
type Client interface {
|
||||
// Begins SASL authentication with the server. It returns the
|
||||
// authentication mechanism name and "initial response" data (if required by
|
||||
// the selected mechanism). A non-nil error causes the client to abort the
|
||||
// authentication attempt.
|
||||
//
|
||||
// A nil ir value is different from a zero-length value. The nil value
|
||||
// indicates that the selected mechanism does not use an initial response,
|
||||
// while a zero-length value indicates an empty initial response, which must
|
||||
// be sent to the server.
|
||||
Start() (mech string, ir []byte, err error)
|
||||
|
||||
// Continues challenge-response authentication. A non-nil error causes
|
||||
// the client to abort the authentication attempt.
|
||||
Next(challenge []byte) (response []byte, err error)
|
||||
}
|
||||
|
||||
// Server interface to perform challenge-response authentication.
|
||||
type Server interface {
|
||||
// Begins or continues challenge-response authentication. If the client
|
||||
// supplies an initial response, response is non-nil.
|
||||
//
|
||||
// If the authentication is finished, done is set to true. If the
|
||||
// authentication has failed, an error is returned.
|
||||
Next(response []byte) (challenge []byte, done bool, err error)
|
||||
}
|
12
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
12
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
Normal file
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
go.sum linguist-generated
|
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# go test -c output
|
||||
*.test
|
||||
*.test.exe
|
||||
|
||||
# Output of go build ./cmd/fsnotify
|
||||
/fsnotify
|
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
Normal file
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
Chris Howey <howeyc@gmail.com> <chris@howey.me>
|
||||
Nathan Youngman <git@nathany.com> <4566+nathany@users.noreply.github.com>
|
470
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
470
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,470 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
Nothing yet.
|
||||
|
||||
## [1.6.0] - 2022-10-13
|
||||
|
||||
This version of fsnotify needs Go 1.16 (this was already the case since 1.5.1,
|
||||
but not documented). It also increases the minimum Linux version to 2.6.32.
|
||||
|
||||
### Additions
|
||||
|
||||
- all: add `Event.Has()` and `Op.Has()` ([#477])
|
||||
|
||||
This makes checking events a lot easier; for example:
|
||||
|
||||
if event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||
}
|
||||
|
||||
Becomes:
|
||||
|
||||
if event.Has(Write) && !event.Has(Remove) {
|
||||
}
|
||||
|
||||
- all: add cmd/fsnotify ([#463])
|
||||
|
||||
A command-line utility for testing and some examples.
|
||||
|
||||
### Changes and fixes
|
||||
|
||||
- inotify: don't ignore events for files that don't exist ([#260], [#470])
|
||||
|
||||
Previously the inotify watcher would call `os.Lstat()` to check if a file
|
||||
still exists before emitting events.
|
||||
|
||||
This was inconsistent with other platforms and resulted in inconsistent event
|
||||
reporting (e.g. when a file is quickly removed and re-created), and generally
|
||||
a source of confusion. It was added in 2013 to fix a memory leak that no
|
||||
longer exists.
|
||||
|
||||
- all: return `ErrNonExistentWatch` when `Remove()` is called on a path that's
|
||||
not watched ([#460])
|
||||
|
||||
- inotify: replace epoll() with non-blocking inotify ([#434])
|
||||
|
||||
Non-blocking inotify was not generally available at the time this library was
|
||||
written in 2014, but now it is. As a result, the minimum Linux version is
|
||||
bumped from 2.6.27 to 2.6.32. This hugely simplifies the code and is faster.
|
||||
|
||||
- kqueue: don't check for events every 100ms ([#480])
|
||||
|
||||
The watcher would wake up every 100ms, even when there was nothing to do. Now
|
||||
it waits until there is something to do.
|
||||
|
||||
- macos: retry opening files on EINTR ([#475])
|
||||
|
||||
- kqueue: skip unreadable files ([#479])
|
||||
|
||||
kqueue requires a file descriptor for every file in a directory; this would
|
||||
fail if a file was unreadable by the current user. Now these files are simply
|
||||
skipped.
|
||||
|
||||
- windows: fix renaming a watched directory if the parent is also watched ([#370])
|
||||
|
||||
- windows: increase buffer size from 4K to 64K ([#485])
|
||||
|
||||
- windows: close file handle on Remove() ([#288])
|
||||
|
||||
- kqueue: put pathname in the error if watching a file fails ([#471])
|
||||
|
||||
- inotify, windows: calling Close() more than once could race ([#465])
|
||||
|
||||
- kqueue: improve Close() performance ([#233])
|
||||
|
||||
- all: various documentation additions and clarifications.
|
||||
|
||||
[#233]: https://github.com/fsnotify/fsnotify/pull/233
|
||||
[#260]: https://github.com/fsnotify/fsnotify/pull/260
|
||||
[#288]: https://github.com/fsnotify/fsnotify/pull/288
|
||||
[#370]: https://github.com/fsnotify/fsnotify/pull/370
|
||||
[#434]: https://github.com/fsnotify/fsnotify/pull/434
|
||||
[#460]: https://github.com/fsnotify/fsnotify/pull/460
|
||||
[#463]: https://github.com/fsnotify/fsnotify/pull/463
|
||||
[#465]: https://github.com/fsnotify/fsnotify/pull/465
|
||||
[#470]: https://github.com/fsnotify/fsnotify/pull/470
|
||||
[#471]: https://github.com/fsnotify/fsnotify/pull/471
|
||||
[#475]: https://github.com/fsnotify/fsnotify/pull/475
|
||||
[#477]: https://github.com/fsnotify/fsnotify/pull/477
|
||||
[#479]: https://github.com/fsnotify/fsnotify/pull/479
|
||||
[#480]: https://github.com/fsnotify/fsnotify/pull/480
|
||||
[#485]: https://github.com/fsnotify/fsnotify/pull/485
|
||||
|
||||
## [1.5.4] - 2022-04-25
|
||||
|
||||
* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447)
|
||||
* go.mod: use latest x/sys [#444](https://github.com/fsnotify/fsnotify/pull/444)
|
||||
* Fix compilation for OpenBSD [#443](https://github.com/fsnotify/fsnotify/pull/443)
|
||||
|
||||
## [1.5.3] - 2022-04-22
|
||||
|
||||
* This version is retracted. An incorrect branch is published accidentally [#445](https://github.com/fsnotify/fsnotify/issues/445)
|
||||
|
||||
## [1.5.2] - 2022-04-21
|
||||
|
||||
* Add a feature to return the directories and files that are being monitored [#374](https://github.com/fsnotify/fsnotify/pull/374)
|
||||
* Fix potential crash on windows if `raw.FileNameLength` exceeds `syscall.MAX_PATH` [#361](https://github.com/fsnotify/fsnotify/pull/361)
|
||||
* Allow build on unsupported GOOS [#424](https://github.com/fsnotify/fsnotify/pull/424)
|
||||
* Don't set `poller.fd` twice in `newFdPoller` [#406](https://github.com/fsnotify/fsnotify/pull/406)
|
||||
* fix go vet warnings: call to `(*T).Fatalf` from a non-test goroutine [#416](https://github.com/fsnotify/fsnotify/pull/416)
|
||||
|
||||
## [1.5.1] - 2021-08-24
|
||||
|
||||
* Revert Add AddRaw to not follow symlinks [#394](https://github.com/fsnotify/fsnotify/pull/394)
|
||||
|
||||
## [1.5.0] - 2021-08-20
|
||||
|
||||
* Go: Increase minimum required version to Go 1.12 [#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||
* Feature: Add AddRaw method which does not follow symlinks when adding a watch [#289](https://github.com/fsnotify/fsnotify/pull/298)
|
||||
* Windows: Follow symlinks by default like on all other systems [#289](https://github.com/fsnotify/fsnotify/pull/289)
|
||||
* CI: Use GitHub Actions for CI and cover go 1.12-1.17
|
||||
[#378](https://github.com/fsnotify/fsnotify/pull/378)
|
||||
[#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||
[#385](https://github.com/fsnotify/fsnotify/pull/385)
|
||||
* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325)
|
||||
|
||||
## [1.4.9] - 2020-03-11
|
||||
|
||||
* Move example usage to the readme #329. This may resolve #328.
|
||||
|
||||
## [1.4.8] - 2020-03-10
|
||||
|
||||
* CI: test more go versions (@nathany 1d13583d846ea9d66dcabbfefbfb9d8e6fb05216)
|
||||
* Tests: Queued inotify events could have been read by the test before max_queued_events was hit (@matthias-stone #265)
|
||||
* Tests: t.Fatalf -> t.Errorf in go routines (@gdey #266)
|
||||
* CI: Less verbosity (@nathany #267)
|
||||
* Tests: Darwin: Exchangedata is deprecated on 10.13 (@nathany #267)
|
||||
* Tests: Check if channels are closed in the example (@alexeykazakov #244)
|
||||
* CI: Only run golint on latest version of go and fix issues (@cpuguy83 #284)
|
||||
* CI: Add windows to travis matrix (@cpuguy83 #284)
|
||||
* Docs: Remover appveyor badge (@nathany 11844c0959f6fff69ba325d097fce35bd85a8e93)
|
||||
* Linux: create epoll and pipe fds with close-on-exec (@JohannesEbke #219)
|
||||
* Linux: open files with close-on-exec (@linxiulei #273)
|
||||
* Docs: Plan to support fanotify (@nathany ab058b44498e8b7566a799372a39d150d9ea0119 )
|
||||
* Project: Add go.mod (@nathany #309)
|
||||
* Project: Revise editor config (@nathany #309)
|
||||
* Project: Update copyright for 2019 (@nathany #309)
|
||||
* CI: Drop go1.8 from CI matrix (@nathany #309)
|
||||
* Docs: Updating the FAQ section for supportability with NFS & FUSE filesystems (@Pratik32 4bf2d1fec78374803a39307bfb8d340688f4f28e )
|
||||
|
||||
## [1.4.7] - 2018-01-09
|
||||
|
||||
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
||||
* Linux: Fix deadlock in Remove (thanks @aarondl)
|
||||
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
|
||||
* Docs: Moved FAQ into the README (thanks @vahe)
|
||||
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
||||
* Docs: replace references to OS X with macOS
|
||||
|
||||
## [1.4.2] - 2016-10-10
|
||||
|
||||
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||
|
||||
## [1.4.1] - 2016-10-04
|
||||
|
||||
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||
|
||||
## [1.4.0] - 2016-10-01
|
||||
|
||||
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||
|
||||
## [1.3.1] - 2016-06-28
|
||||
|
||||
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||
|
||||
## [1.3.0] - 2016-04-19
|
||||
|
||||
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||
|
||||
## [1.2.10] - 2016-03-02
|
||||
|
||||
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||
|
||||
## [1.2.9] - 2016-01-13
|
||||
|
||||
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||
|
||||
## [1.2.8] - 2015-12-17
|
||||
|
||||
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||
* inotify: fix race in test
|
||||
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||
|
||||
## [1.2.5] - 2015-10-17
|
||||
|
||||
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||
|
||||
## [1.2.1] - 2015-10-14
|
||||
|
||||
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||
|
||||
## [1.2.0] - 2015-02-08
|
||||
|
||||
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||
|
||||
## [1.1.1] - 2015-02-05
|
||||
|
||||
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||
|
||||
## [1.1.0] - 2014-12-12
|
||||
|
||||
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||
* add low-level functions
|
||||
* only need to store flags on directories
|
||||
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
||||
* done can be an unbuffered channel
|
||||
* remove calls to os.NewSyscallError
|
||||
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
||||
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||
|
||||
## [1.0.4] - 2014-09-07
|
||||
|
||||
* kqueue: add dragonfly to the build tags.
|
||||
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||
|
||||
## [1.0.3] - 2014-08-19
|
||||
|
||||
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||
|
||||
## [1.0.2] - 2014-08-17
|
||||
|
||||
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||
|
||||
## [1.0.0] - 2014-08-15
|
||||
|
||||
* [API] Remove AddWatch on Windows, use Add.
|
||||
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||
* Minor updates based on feedback from golint.
|
||||
|
||||
## dev / 2014-07-09
|
||||
|
||||
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
||||
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
||||
|
||||
## dev / 2014-07-04
|
||||
|
||||
* kqueue: fix incorrect mutex used in Close()
|
||||
* Update example to demonstrate usage of Op.
|
||||
|
||||
## dev / 2014-06-28
|
||||
|
||||
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
||||
* Fix for String() method on Event (thanks Alex Brainman)
|
||||
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
||||
|
||||
## dev / 2014-06-21
|
||||
|
||||
* Events channel of type Event rather than *Event.
|
||||
* [internal] use syscall constants directly for inotify and kqueue.
|
||||
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
||||
|
||||
## dev / 2014-06-19
|
||||
|
||||
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
||||
* [internal] remove cookie from Event struct (unused).
|
||||
* [internal] Event struct has the same definition across every OS.
|
||||
* [internal] remove internal watch and removeWatch methods.
|
||||
|
||||
## dev / 2014-06-12
|
||||
|
||||
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
||||
* [API] Pluralized channel names: Events and Errors.
|
||||
* [API] Renamed FileEvent struct to Event.
|
||||
* [API] Op constants replace methods like IsCreate().
|
||||
|
||||
## dev / 2014-06-12
|
||||
|
||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||
|
||||
## dev / 2014-05-23
|
||||
|
||||
* [API] Remove current implementation of WatchFlags.
|
||||
* current implementation doesn't take advantage of OS for efficiency
|
||||
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
||||
* no tests for the current implementation
|
||||
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||
|
||||
## [0.9.3] - 2014-12-31
|
||||
|
||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||
|
||||
## [0.9.2] - 2014-08-17
|
||||
|
||||
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||
|
||||
## [0.9.1] - 2014-06-12
|
||||
|
||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||
|
||||
## [0.9.0] - 2014-01-17
|
||||
|
||||
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||
|
||||
## [0.8.12] - 2013-11-13
|
||||
|
||||
* [API] Remove FD_SET and friends from Linux adapter
|
||||
|
||||
## [0.8.11] - 2013-11-02
|
||||
|
||||
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
||||
|
||||
## [0.8.10] - 2013-10-19
|
||||
|
||||
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||
|
||||
## [0.8.9] - 2013-09-08
|
||||
|
||||
* [Doc] Contributing (thanks @nathany)
|
||||
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||
|
||||
## [0.8.8] - 2013-06-17
|
||||
|
||||
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||
|
||||
## [0.8.7] - 2013-06-03
|
||||
|
||||
* [API] Make syscall flags internal
|
||||
* [Fix] inotify: ignore event changes
|
||||
* [Fix] race in symlink test [#45][] (reported by @srid)
|
||||
* [Fix] tests on Windows
|
||||
* lower case error messages
|
||||
|
||||
## [0.8.6] - 2013-05-23
|
||||
|
||||
* kqueue: Use EVT_ONLY flag on Darwin
|
||||
* [Doc] Update README with full example
|
||||
|
||||
## [0.8.5] - 2013-05-09
|
||||
|
||||
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||
|
||||
## [0.8.4] - 2013-04-07
|
||||
|
||||
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||
|
||||
## [0.8.3] - 2013-03-13
|
||||
|
||||
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||
|
||||
## [0.8.2] - 2013-02-07
|
||||
|
||||
* [Doc] add Authors
|
||||
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||
|
||||
## [0.8.1] - 2013-01-09
|
||||
|
||||
* [Fix] Windows path separators
|
||||
* [Doc] BSD License
|
||||
|
||||
## [0.8.0] - 2012-11-09
|
||||
|
||||
* kqueue: directory watching improvements (thanks @vmirage)
|
||||
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||
|
||||
## [0.7.4] - 2012-10-09
|
||||
|
||||
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||
* [Fix] kqueue: modify after recreation of file
|
||||
|
||||
## [0.7.3] - 2012-09-27
|
||||
|
||||
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||
|
||||
## [0.7.2] - 2012-09-01
|
||||
|
||||
* kqueue: events for created directories
|
||||
|
||||
## [0.7.1] - 2012-07-14
|
||||
|
||||
* [Fix] for renaming files
|
||||
|
||||
## [0.7.0] - 2012-07-02
|
||||
|
||||
* [Feature] FSNotify flags
|
||||
* [Fix] inotify: Added file name back to event path
|
||||
|
||||
## [0.6.0] - 2012-06-06
|
||||
|
||||
* kqueue: watch files after directory created (thanks @tmc)
|
||||
|
||||
## [0.5.1] - 2012-05-22
|
||||
|
||||
* [Fix] inotify: remove all watches before Close()
|
||||
|
||||
## [0.5.0] - 2012-05-03
|
||||
|
||||
* [API] kqueue: return errors during watch instead of sending over channel
|
||||
* kqueue: match symlink behavior on Linux
|
||||
* inotify: add `DELETE_SELF` (requested by @taralx)
|
||||
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||
|
||||
## [0.4.0] - 2012-03-30
|
||||
|
||||
* Go 1 released: build with go tool
|
||||
* [Feature] Windows support using winfsnotify
|
||||
* Windows does not have attribute change notifications
|
||||
* Roll attribute notifications into IsModify
|
||||
|
||||
## [0.3.0] - 2012-02-19
|
||||
|
||||
* kqueue: add files when watch directory
|
||||
|
||||
## [0.2.0] - 2011-12-30
|
||||
|
||||
* update to latest Go weekly code
|
||||
|
||||
## [0.1.0] - 2011-10-19
|
||||
|
||||
* kqueue: add watch on file creation to match inotify
|
||||
* kqueue: create file event
|
||||
* inotify: ignore `IN_IGNORED` events
|
||||
* event String()
|
||||
* linux: common FileEvent functions
|
||||
* initial commit
|
||||
|
||||
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
||||
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
||||
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
||||
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
||||
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
||||
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
||||
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
||||
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
||||
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
||||
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
||||
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
||||
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
||||
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
||||
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
||||
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
||||
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
26
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
26
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
Thank you for your interest in contributing to fsnotify! We try to review and
|
||||
merge PRs in a reasonable timeframe, but please be aware that:
|
||||
|
||||
- To avoid "wasted" work, please discus changes on the issue tracker first. You
|
||||
can just send PRs, but they may end up being rejected for one reason or the
|
||||
other.
|
||||
|
||||
- fsnotify is a cross-platform library, and changes must work reasonably well on
|
||||
all supported platforms.
|
||||
|
||||
- Changes will need to be compatible; old code should still compile, and the
|
||||
runtime behaviour can't change in ways that are likely to lead to problems for
|
||||
users.
|
||||
|
||||
Testing
|
||||
-------
|
||||
Just `go test ./...` runs all the tests; the CI runs this on all supported
|
||||
platforms. Testing different platforms locally can be done with something like
|
||||
[goon] or [Vagrant], but this isn't super-easy to set up at the moment.
|
||||
|
||||
Use the `-short` flag to make the "stress test" run faster.
|
||||
|
||||
|
||||
[goon]: https://github.com/arp242/goon
|
||||
[Vagrant]: https://www.vagrantup.com/
|
||||
[integration_test.go]: /integration_test.go
|
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
Copyright © 2012 The Go Authors. All rights reserved.
|
||||
Copyright © fsnotify Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
161
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
161
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
fsnotify is a Go library to provide cross-platform filesystem notifications on
|
||||
Windows, Linux, macOS, and BSD systems.
|
||||
|
||||
Go 1.16 or newer is required; the full documentation is at
|
||||
https://pkg.go.dev/github.com/fsnotify/fsnotify
|
||||
|
||||
**It's best to read the documentation at pkg.go.dev, as it's pinned to the last
|
||||
released version, whereas this README is for the last development version which
|
||||
may include additions/changes.**
|
||||
|
||||
---
|
||||
|
||||
Platform support:
|
||||
|
||||
| Adapter | OS | Status |
|
||||
| --------------------- | ---------------| -------------------------------------------------------------|
|
||||
| inotify | Linux 2.6.32+ | Supported |
|
||||
| kqueue | BSD, macOS | Supported |
|
||||
| ReadDirectoryChangesW | Windows | Supported |
|
||||
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
|
||||
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) |
|
||||
| fanotify | Linux 5.9+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||
| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
|
||||
| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
|
||||
|
||||
Linux and macOS should include Android and iOS, but these are currently untested.
|
||||
|
||||
Usage
|
||||
-----
|
||||
A basic example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create new watcher.
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
// Start listening for events.
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("event:", event)
|
||||
if event.Has(fsnotify.Write) {
|
||||
log.Println("modified file:", event.Name)
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("error:", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Add a path.
|
||||
err = watcher.Add("/tmp")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Block main goroutine forever.
|
||||
<-make(chan struct{})
|
||||
}
|
||||
```
|
||||
|
||||
Some more examples can be found in [cmd/fsnotify](cmd/fsnotify), which can be
|
||||
run with:
|
||||
|
||||
% go run ./cmd/fsnotify
|
||||
|
||||
FAQ
|
||||
---
|
||||
### Will a file still be watched when it's moved to another directory?
|
||||
No, not unless you are watching the location it was moved to.
|
||||
|
||||
### Are subdirectories watched too?
|
||||
No, you must add watches for any directory you want to watch (a recursive
|
||||
watcher is on the roadmap: [#18]).
|
||||
|
||||
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
||||
|
||||
### Do I have to watch the Error and Event channels in a goroutine?
|
||||
As of now, yes (you can read both channels in the same goroutine using `select`,
|
||||
you don't need a separate goroutine for both channels; see the example).
|
||||
|
||||
### Why don't notifications work with NFS, SMB, FUSE, /proc, or /sys?
|
||||
fsnotify requires support from underlying OS to work. The current NFS and SMB
|
||||
protocols does not provide network level support for file notifications, and
|
||||
neither do the /proc and /sys virtual filesystems.
|
||||
|
||||
This could be fixed with a polling watcher ([#9]), but it's not yet implemented.
|
||||
|
||||
[#9]: https://github.com/fsnotify/fsnotify/issues/9
|
||||
|
||||
Platform-specific notes
|
||||
-----------------------
|
||||
### Linux
|
||||
When a file is removed a REMOVE event won't be emitted until all file
|
||||
descriptors are closed; it will emit a CHMOD instead:
|
||||
|
||||
fp := os.Open("file")
|
||||
os.Remove("file") // CHMOD
|
||||
fp.Close() // REMOVE
|
||||
|
||||
This is the event that inotify sends, so not much can be changed about this.
|
||||
|
||||
The `fs.inotify.max_user_watches` sysctl variable specifies the upper limit for
|
||||
the number of watches per user, and `fs.inotify.max_user_instances` specifies
|
||||
the maximum number of inotify instances per user. Every Watcher you create is an
|
||||
"instance", and every path you add is a "watch".
|
||||
|
||||
These are also exposed in `/proc` as `/proc/sys/fs/inotify/max_user_watches` and
|
||||
`/proc/sys/fs/inotify/max_user_instances`
|
||||
|
||||
To increase them you can use `sysctl` or write the value to proc file:
|
||||
|
||||
# The default values on Linux 5.18
|
||||
sysctl fs.inotify.max_user_watches=124983
|
||||
sysctl fs.inotify.max_user_instances=128
|
||||
|
||||
To make the changes persist on reboot edit `/etc/sysctl.conf` or
|
||||
`/usr/lib/sysctl.d/50-default.conf` (details differ per Linux distro; check your
|
||||
distro's documentation):
|
||||
|
||||
fs.inotify.max_user_watches=124983
|
||||
fs.inotify.max_user_instances=128
|
||||
|
||||
Reaching the limit will result in a "no space left on device" or "too many open
|
||||
files" error.
|
||||
|
||||
### kqueue (macOS, all BSD systems)
|
||||
kqueue requires opening a file descriptor for every file that's being watched;
|
||||
so if you're watching a directory with five files then that's six file
|
||||
descriptors. You will run in to your system's "max open files" limit faster on
|
||||
these platforms.
|
||||
|
||||
The sysctl variables `kern.maxfiles` and `kern.maxfilesperproc` can be used to
|
||||
control the maximum number of open files.
|
||||
|
||||
### macOS
|
||||
Spotlight indexing on macOS can result in multiple events (see [#15]). A temporary
|
||||
workaround is to add your folder(s) to the *Spotlight Privacy settings* until we
|
||||
have a native FSEvents implementation (see [#11]).
|
||||
|
||||
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
[#15]: https://github.com/fsnotify/fsnotify/issues/15
|
162
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
Normal file
162
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
//go:build solaris
|
||||
// +build solaris
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Watcher watches a set of paths, delivering events on a channel.
|
||||
//
|
||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||
// value).
|
||||
//
|
||||
// # Linux notes
|
||||
//
|
||||
// When a file is removed a Remove event won't be emitted until all file
|
||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||
//
|
||||
// fp := os.Open("file")
|
||||
// os.Remove("file") // Triggers Chmod
|
||||
// fp.Close() // Triggers Remove
|
||||
//
|
||||
// This is the event that inotify sends, so not much can be changed about this.
|
||||
//
|
||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||
// create is an "instance", and every path you add is a "watch".
|
||||
//
|
||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||
// /proc/sys/fs/inotify/max_user_instances
|
||||
//
|
||||
// To increase them you can use sysctl or write the value to the /proc file:
|
||||
//
|
||||
// # Default values on Linux 5.18
|
||||
// sysctl fs.inotify.max_user_watches=124983
|
||||
// sysctl fs.inotify.max_user_instances=128
|
||||
//
|
||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||
// your distro's documentation):
|
||||
//
|
||||
// fs.inotify.max_user_watches=124983
|
||||
// fs.inotify.max_user_instances=128
|
||||
//
|
||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||
// files" error.
|
||||
//
|
||||
// # kqueue notes (macOS, BSD)
|
||||
//
|
||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||
// so if you're watching a directory with five files then that's six file
|
||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||
// these platforms.
|
||||
//
|
||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||
// systems.
|
||||
//
|
||||
// # macOS notes
|
||||
//
|
||||
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||
//
|
||||
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||
type Watcher struct {
|
||||
// Events sends the filesystem change events.
|
||||
//
|
||||
// fsnotify can send the following events; a "path" here can refer to a
|
||||
// file, directory, symbolic link, or special file like a FIFO.
|
||||
//
|
||||
// fsnotify.Create A new path was created; this may be followed by one
|
||||
// or more Write events if data also gets written to a
|
||||
// file.
|
||||
//
|
||||
// fsnotify.Remove A path was removed.
|
||||
//
|
||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||
// old path as Event.Name, and a Create event will be
|
||||
// sent with the new name. Renames are only sent for
|
||||
// paths that are currently watched; e.g. moving an
|
||||
// unmonitored file into a monitored directory will
|
||||
// show up as just a Create. Similarly, renaming a file
|
||||
// to outside a monitored directory will show up as
|
||||
// only a Rename.
|
||||
//
|
||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||
// also trigger a Write. A single "write action"
|
||||
// initiated by the user may show up as one or multiple
|
||||
// writes, depending on when the system syncs things to
|
||||
// disk. For example when compiling a large Go program
|
||||
// you may get hundreds of Write events, so you
|
||||
// probably want to wait until you've stopped receiving
|
||||
// them (see the dedup example in cmd/fsnotify).
|
||||
//
|
||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||
// when a file is removed (or more accurately, when a
|
||||
// link to an inode is removed). On kqueue it's sent
|
||||
// and on kqueue when a file is truncated. On Windows
|
||||
// it's never sent.
|
||||
Events chan Event
|
||||
|
||||
// Errors sends any errors.
|
||||
Errors chan error
|
||||
}
|
||||
|
||||
// NewWatcher creates a new Watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts monitoring the path for changes.
|
||||
//
|
||||
// A path can only be watched once; attempting to watch it more than once will
|
||||
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||
// added. A watch will be automatically removed if the path is deleted.
|
||||
//
|
||||
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||
// re-created, or if it's moved to a different filesystem.
|
||||
//
|
||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||
//
|
||||
// # Watching directories
|
||||
//
|
||||
// All files in a directory are monitored, including new files that are created
|
||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||
// non-recursive).
|
||||
//
|
||||
// # Watching files
|
||||
//
|
||||
// Watching individual files (rather than directories) is generally not
|
||||
// recommended as many tools update files atomically. Instead of "just" writing
|
||||
// to the file a temporary file will be written to first, and if successful the
|
||||
// temporary file is moved to to destination removing the original, or some
|
||||
// variant thereof. The watcher on the original file is now lost, as it no
|
||||
// longer exists.
|
||||
//
|
||||
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||
func (w *Watcher) Add(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops monitoring the path for changes.
|
||||
//
|
||||
// Directories are always removed non-recursively. For example, if you added
|
||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||
//
|
||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
return nil
|
||||
}
|
459
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
Normal file
459
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
Normal file
@ -0,0 +1,459 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Watcher watches a set of paths, delivering events on a channel.
|
||||
//
|
||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||
// value).
|
||||
//
|
||||
// # Linux notes
|
||||
//
|
||||
// When a file is removed a Remove event won't be emitted until all file
|
||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||
//
|
||||
// fp := os.Open("file")
|
||||
// os.Remove("file") // Triggers Chmod
|
||||
// fp.Close() // Triggers Remove
|
||||
//
|
||||
// This is the event that inotify sends, so not much can be changed about this.
|
||||
//
|
||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||
// create is an "instance", and every path you add is a "watch".
|
||||
//
|
||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||
// /proc/sys/fs/inotify/max_user_instances
|
||||
//
|
||||
// To increase them you can use sysctl or write the value to the /proc file:
|
||||
//
|
||||
// # Default values on Linux 5.18
|
||||
// sysctl fs.inotify.max_user_watches=124983
|
||||
// sysctl fs.inotify.max_user_instances=128
|
||||
//
|
||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||
// your distro's documentation):
|
||||
//
|
||||
// fs.inotify.max_user_watches=124983
|
||||
// fs.inotify.max_user_instances=128
|
||||
//
|
||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||
// files" error.
|
||||
//
|
||||
// # kqueue notes (macOS, BSD)
|
||||
//
|
||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||
// so if you're watching a directory with five files then that's six file
|
||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||
// these platforms.
|
||||
//
|
||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||
// systems.
|
||||
//
|
||||
// # macOS notes
|
||||
//
|
||||
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
|
||||
// Settings" until we have a native FSEvents implementation (see [#11]).
|
||||
//
|
||||
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||
type Watcher struct {
|
||||
// Events sends the filesystem change events.
|
||||
//
|
||||
// fsnotify can send the following events; a "path" here can refer to a
|
||||
// file, directory, symbolic link, or special file like a FIFO.
|
||||
//
|
||||
// fsnotify.Create A new path was created; this may be followed by one
|
||||
// or more Write events if data also gets written to a
|
||||
// file.
|
||||
//
|
||||
// fsnotify.Remove A path was removed.
|
||||
//
|
||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||
// old path as Event.Name, and a Create event will be
|
||||
// sent with the new name. Renames are only sent for
|
||||
// paths that are currently watched; e.g. moving an
|
||||
// unmonitored file into a monitored directory will
|
||||
// show up as just a Create. Similarly, renaming a file
|
||||
// to outside a monitored directory will show up as
|
||||
// only a Rename.
|
||||
//
|
||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||
// also trigger a Write. A single "write action"
|
||||
// initiated by the user may show up as one or multiple
|
||||
// writes, depending on when the system syncs things to
|
||||
// disk. For example when compiling a large Go program
|
||||
// you may get hundreds of Write events, so you
|
||||
// probably want to wait until you've stopped receiving
|
||||
// them (see the dedup example in cmd/fsnotify).
|
||||
//
|
||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||
// when a file is removed (or more accurately, when a
|
||||
// link to an inode is removed). On kqueue it's sent
|
||||
// and on kqueue when a file is truncated. On Windows
|
||||
// it's never sent.
|
||||
Events chan Event
|
||||
|
||||
// Errors sends any errors.
|
||||
Errors chan error
|
||||
|
||||
// Store fd here as os.File.Read() will no longer return on close after
|
||||
// calling Fd(). See: https://github.com/golang/go/issues/26439
|
||||
fd int
|
||||
mu sync.Mutex // Map access
|
||||
inotifyFile *os.File
|
||||
watches map[string]*watch // Map of inotify watches (key: path)
|
||||
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
doneResp chan struct{} // Channel to respond to Close
|
||||
}
|
||||
|
||||
// NewWatcher creates a new Watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
// Create inotify fd
|
||||
// Need to set the FD to nonblocking mode in order for SetDeadline methods to work
|
||||
// Otherwise, blocking i/o operations won't terminate on close
|
||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
||||
if fd == -1 {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
w := &Watcher{
|
||||
fd: fd,
|
||||
inotifyFile: os.NewFile(uintptr(fd), ""),
|
||||
watches: make(map[string]*watch),
|
||||
paths: make(map[int]string),
|
||||
Events: make(chan Event),
|
||||
Errors: make(chan error),
|
||||
done: make(chan struct{}),
|
||||
doneResp: make(chan struct{}),
|
||||
}
|
||||
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Returns true if the event was sent, or false if watcher is closed.
|
||||
func (w *Watcher) sendEvent(e Event) bool {
|
||||
select {
|
||||
case w.Events <- e:
|
||||
return true
|
||||
case <-w.done:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true if the error was sent, or false if watcher is closed.
|
||||
func (w *Watcher) sendError(err error) bool {
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
return true
|
||||
case <-w.done:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
w.mu.Lock()
|
||||
if w.isClosed() {
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||
close(w.done)
|
||||
w.mu.Unlock()
|
||||
|
||||
// Causes any blocking reads to return with an error, provided the file
|
||||
// still supports deadline operations.
|
||||
err := w.inotifyFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for goroutine to close
|
||||
<-w.doneResp
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts monitoring the path for changes.
|
||||
//
|
||||
// A path can only be watched once; attempting to watch it more than once will
|
||||
// return an error. Paths that do not yet exist on the filesystem cannot be
|
||||
// added. A watch will be automatically removed if the path is deleted.
|
||||
//
|
||||
// A path will remain watched if it gets renamed to somewhere else on the same
|
||||
// filesystem, but the monitor will get removed if the path gets deleted and
|
||||
// re-created, or if it's moved to a different filesystem.
|
||||
//
|
||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||
//
|
||||
// # Watching directories
|
||||
//
|
||||
// All files in a directory are monitored, including new files that are created
|
||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||
// non-recursive).
|
||||
//
|
||||
// # Watching files
|
||||
//
|
||||
// Watching individual files (rather than directories) is generally not
|
||||
// recommended as many tools update files atomically. Instead of "just" writing
|
||||
// to the file a temporary file will be written to first, and if successful the
|
||||
// temporary file is moved to to destination removing the original, or some
|
||||
// variant thereof. The watcher on the original file is now lost, as it no
|
||||
// longer exists.
|
||||
//
|
||||
// Instead, watch the parent directory and use Event.Name to filter out files
|
||||
// you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
|
||||
func (w *Watcher) Add(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
if w.isClosed() {
|
||||
return errors.New("inotify instance already closed")
|
||||
}
|
||||
|
||||
var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
||||
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
||||
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watchEntry := w.watches[name]
|
||||
if watchEntry != nil {
|
||||
flags |= watchEntry.flags | unix.IN_MASK_ADD
|
||||
}
|
||||
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
||||
if wd == -1 {
|
||||
return errno
|
||||
}
|
||||
|
||||
if watchEntry == nil {
|
||||
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
||||
w.paths[wd] = name
|
||||
} else {
|
||||
watchEntry.wd = uint32(wd)
|
||||
watchEntry.flags = flags
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops monitoring the path for changes.
|
||||
//
|
||||
// Directories are always removed non-recursively. For example, if you added
|
||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||
//
|
||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
|
||||
// Fetch the watch.
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watch, ok := w.watches[name]
|
||||
|
||||
// Remove it from inotify.
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||||
}
|
||||
|
||||
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
||||
// error, we need to clean up our internal state to ensure it matches
|
||||
// inotify's kernel state.
|
||||
delete(w.paths, int(watch.wd))
|
||||
delete(w.watches, name)
|
||||
|
||||
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||
// the inotify will already have been removed.
|
||||
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||
// by another thread and we have not received IN_IGNORE event.
|
||||
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
||||
if success == -1 {
|
||||
// TODO: Perhaps it's not helpful to return an error here in every case;
|
||||
// The only two possible errors are:
|
||||
//
|
||||
// - EBADF, which happens when w.fd is not a valid file descriptor
|
||||
// of any kind.
|
||||
// - EINVAL, which is when fd is not an inotify descriptor or wd
|
||||
// is not a valid watch descriptor. Watch descriptors are
|
||||
// invalidated when they are removed explicitly or implicitly;
|
||||
// explicitly by inotify_rm_watch, implicitly when the file they
|
||||
// are watching is deleted.
|
||||
return errno
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WatchList returns all paths added with [Add] (and are not yet removed).
|
||||
func (w *Watcher) WatchList() []string {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
entries := make([]string, 0, len(w.watches))
|
||||
for pathname := range w.watches {
|
||||
entries = append(entries, pathname)
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
type watch struct {
|
||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||
}
|
||||
|
||||
// readEvents reads from the inotify file descriptor, converts the
|
||||
// received events into Event objects and sends them via the Events channel
|
||||
func (w *Watcher) readEvents() {
|
||||
defer func() {
|
||||
close(w.doneResp)
|
||||
close(w.Errors)
|
||||
close(w.Events)
|
||||
}()
|
||||
|
||||
var (
|
||||
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
errno error // Syscall errno
|
||||
)
|
||||
for {
|
||||
// See if we have been closed.
|
||||
if w.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
n, err := w.inotifyFile.Read(buf[:])
|
||||
switch {
|
||||
case errors.Unwrap(err) == os.ErrClosed:
|
||||
return
|
||||
case err != nil:
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if n < unix.SizeofInotifyEvent {
|
||||
var err error
|
||||
if n == 0 {
|
||||
// If EOF is received. This should really never happen.
|
||||
err = io.EOF
|
||||
} else if n < 0 {
|
||||
// If an error occurred while reading.
|
||||
err = errno
|
||||
} else {
|
||||
// Read was too short.
|
||||
err = errors.New("notify: short read in readEvents()")
|
||||
}
|
||||
if !w.sendError(err) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var offset uint32
|
||||
// We don't know how many events we just read into the buffer
|
||||
// While the offset points to at least one whole event...
|
||||
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||
var (
|
||||
// Point "raw" to the event in the buffer
|
||||
raw = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
mask = uint32(raw.Mask)
|
||||
nameLen = uint32(raw.Len)
|
||||
)
|
||||
|
||||
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||
if !w.sendError(ErrEventOverflow) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If the event happened to the watched directory or the watched file, the kernel
|
||||
// doesn't append the filename to the event, but we would like to always fill the
|
||||
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||
// the "paths" map.
|
||||
w.mu.Lock()
|
||||
name, ok := w.paths[int(raw.Wd)]
|
||||
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
||||
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
||||
// with the inotify kernel state which has already deleted the watch
|
||||
// automatically.
|
||||
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||
delete(w.paths, int(raw.Wd))
|
||||
delete(w.watches, name)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
if nameLen > 0 {
|
||||
// Point "bytes" at the first byte of the filename
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||
}
|
||||
|
||||
event := w.newEvent(name, mask)
|
||||
|
||||
// Send the events that are not ignored on the events channel
|
||||
if mask&unix.IN_IGNORED == 0 {
|
||||
if !w.sendEvent(event) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
offset += unix.SizeofInotifyEvent + nameLen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newEvent returns an platform-independent Event based on an inotify mask.
|
||||
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||
e.Op |= Create
|
||||
}
|
||||
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user