Compare commits

..

118 Commits

Author SHA1 Message Date
微凉 b1e662cd34 🐛 fix onedrive slow response 2021-11-28 17:01:27 +08:00
微凉 0f0e1104a4 WebDAV auth 2021-11-28 16:18:09 +08:00
微凉 3041da35ab 🐛 fix webdav proxy link auth 2021-11-28 16:05:41 +08:00
微凉 9eab54a7c8 webdav(view only) 2021-11-28 15:10:48 +08:00
微凉 0b8d3a0a2c 🐛 fix can't get file 2021-11-28 15:10:06 +08:00
微凉 f9945a14a8 🎇 webdav unfinished 2021-11-28 00:12:04 +08:00
微凉 c39752ceb4 🎇 optimization googledrive 2021-11-27 20:40:03 +08:00
微凉 53b383d2cf 🔨 refactor driver: onedrive 2021-11-27 20:20:14 +08:00
微凉 e76fc3e616 🔨 refactor driver: native 2021-11-27 20:07:32 +08:00
微凉 eb21b87020 🔨 refactor driver: google 2021-11-27 19:52:38 +08:00
微凉 f577d82242 🔨 refactor driver: ali 2021-11-27 19:40:36 +08:00
微凉 98691b2aa8 🔨 refactor driver(123 and 189) 2021-11-27 18:55:09 +08:00
微凉 4fe6ed6c3e 🐛 fix log 2021-11-26 20:23:33 +08:00
微凉 fe73ece57d change log 2021-11-26 20:20:32 +08:00
微凉 59b8f1084a 🐛 fixed checking the parent folder when checked the password of the current folder 2021-11-25 16:24:39 +08:00
微凉 2f669ac45c 📝 update doc 2021-11-24 18:58:17 +08:00
微凉 d03d91d518 🐛 fix google drive root folder label 2021-11-24 13:34:15 +08:00
微凉 fe981f67ec 🐛 fix windows check parent 2021-11-23 16:22:23 +08:00
微凉 8cfabfd0f5 resolved #170 native driver sort 2021-11-23 16:09:42 +08:00
微凉 163ee1159e change path interface 2021-11-23 15:58:03 +08:00
微凉 e31402e94f resolved #169 2021-11-23 15:46:10 +08:00
微凉 5500980d63 🐛 fix onedrive get files 2021-11-23 15:39:09 +08:00
微凉 b1695445e0 🐛 index add write header now 2021-11-22 18:10:09 +08:00
微凉 5db1ad4adf 🎇 resolved #160 2021-11-17 22:19:11 +08:00
微凉 725f5b0c55 support 123pan 2021-11-17 22:17:04 +08:00
微凉 87a74394b3 google client retry 2021-11-17 20:40:42 +08:00
微凉 a41c820525 🐛 fix debug output 2021-11-17 20:29:01 +08:00
微凉 cd53dc6d24 📝 update readme 2021-11-17 17:37:41 +08:00
微凉 cfe16b5ed2 add GoogleDrive 2021-11-17 17:21:11 +08:00
微凉 3d9746485d change proxy setting 2021-11-17 17:20:57 +08:00
微凉 0b7f2fee7d proxy add account 2021-11-17 16:37:12 +08:00
微凉 36d52e0b75 delete cache file 2021-11-17 16:25:32 +08:00
微凉 e4d254e4b0 simplify delete account when change name 2021-11-17 15:25:31 +08:00
微凉 e8d27a30b4 🐛 fix delete account when change name 2021-11-17 15:21:51 +08:00
微凉 69514668cc 🎇 replace title 2021-11-16 22:32:05 +08:00
微凉 e5f8f59c87 🐛 fix native account status 2021-11-16 18:23:59 +08:00
微凉 f87ee1ed9e 🐛 fix 189 account status 2021-11-16 15:08:55 +08:00
微凉 07155cfd01 add show current password 2021-11-16 15:00:56 +08:00
微凉 5e982980dc 🐛 fix determine gbk 2021-11-16 00:03:49 +08:00
微凉 8987958e26 🐛 fix 189 size and time 2021-11-15 20:31:44 +08:00
微凉 4466cb19a5 🎇 get 189 redirect url 2021-11-15 20:15:25 +08:00
微凉 da74e29b26 🐛 fix gbk garbled 2021-11-15 19:20:39 +08:00
微凉 82272fcbf5 🐛 fix windows onedrive path 2021-11-15 19:07:04 +08:00
微凉 74d86f8cc4 🎇 delete useless cookieJar 2021-11-15 19:06:44 +08:00
微凉 c03646dedf 🐛 change text proxy 2021-11-15 19:06:10 +08:00
微凉 c0d1888e25 support 189cloud(login refer to PanIndex) 2021-11-15 18:00:43 +08:00
微凉 f4942e89bd 🐛 fix account status 2021-11-15 15:01:14 +08:00
微凉 27e61c9eb8 🐛 fix change name error 2021-11-15 14:54:22 +08:00
微凉 aeb72320ca 🐛 fix get error account 2021-11-15 14:53:32 +08:00
微凉 caddba05e9 💚 remove ppc64le docker 2021-11-14 14:53:23 +08:00
微凉 0dffb9aaa1 add customize style / script 2021-11-14 14:37:26 +08:00
微凉 0f93d2bfed 🐛 fix version type 2021-11-14 00:30:15 +08:00
微凉 b785945210 🐛 fix add account 2021-11-13 19:31:35 +08:00
微凉 48a65784c7 🐛 fix can't delete and get meta 2021-11-13 17:10:44 +08:00
微凉 5f34b8ab80 account and meta add id 2021-11-13 16:49:03 +08:00
微凉 959ef620fb check parent folder password 2021-11-13 16:12:12 +08:00
微凉 a73501463f fix proxy 2021-11-13 16:02:19 +08:00
微凉 cf07b3921c 🔨 switch fiber to gin 2021-11-13 15:53:26 +08:00
微凉 65ec4e3611 dockerfile volume 2021-11-13 14:23:41 +08:00
微凉 7799c1f3bc 🎇 change default setting 2021-11-12 23:03:39 +08:00
微凉 e902c2ded7 add settings 2021-11-12 22:56:30 +08:00
微凉 2c675ae909 change settings 2021-11-12 19:39:01 +08:00
微凉 98c017730f resolved #155 2021-11-11 19:51:25 +08:00
微凉 f4affb2c69 add go proxy 2021-11-09 21:36:27 +08:00
微凉 17af21079f 💚 fix web path 2021-11-09 19:35:46 +08:00
微凉 f7d35ec925 💚 fix dockerfile 2021-11-09 18:12:36 +08:00
微凉 a8730e82b5 💚 fix dockerfile 2021-11-09 18:02:15 +08:00
微凉 6275e27d1b 💚 fix dockerfile 2021-11-09 17:54:38 +08:00
微凉 e70353704f 💚 fix dockerfile 2021-11-09 17:22:58 +08:00
微凉 be5b1e42d4 💚 fix dockerfile 2021-11-09 16:51:30 +08:00
微凉 7d08cbc4a9 🚧 build docker 2021-11-09 16:46:03 +08:00
微凉 1542878d66 🎨 format code 2021-11-09 16:03:04 +08:00
微凉 ac8f5d5737 🐛 file name contains + 2021-11-07 23:05:50 +08:00
微凉 9ed5b6e581 🐛 file name contains + 2021-11-07 22:12:52 +08:00
微凉 db7bff2d61 🐛 fix: #151 2021-11-07 14:04:54 +08:00
微凉 7970e737f0 add markdown theme setting 2021-11-06 17:33:00 +08:00
微凉 d4523d52ee 🐛 delete timed task 2021-11-06 17:25:07 +08:00
微凉 ac8476702c 🐛 refer to https://github.com/ad-m/github-push-action/issues/44#issuecomment-581706892 2021-11-05 20:52:50 +08:00
微凉 12f68eaed9 💚 fix upload asserts 2021-11-05 19:47:02 +08:00
微凉 6a51f02845 💚 mv build.sh 2021-11-05 19:11:39 +08:00
微凉 e7071e1093 release asserts files cdn 2021-11-05 19:03:54 +08:00
微凉 11b141b190 update onedrive account status 2021-11-05 16:32:20 +08:00
微凉 7e099b39cf cache len(files)=0: request 2021-11-05 16:30:50 +08:00
微凉 b46bf0dfc9 🐛 delete beta1 meta 2021-11-04 23:25:53 +08:00
微凉 91f64161b2 💚 change back to ubuntu 2021-11-04 22:38:08 +08:00
微凉 8255ef4346 💚 print md5 2021-11-04 21:34:52 +08:00
微凉 254b6c6f79 print build version 2021-11-04 18:42:27 +08:00
微凉 1c56d27e20 💚 fix macOS build 2021-11-04 16:54:35 +08:00
微凉 4da655cc44 💚 change release platform 2021-11-04 16:33:31 +08:00
微凉 a746324ecf 💚 brew 2021-11-04 14:03:45 +08:00
微凉 1a3b931727 💚 setup docker 2021-11-04 13:34:10 +08:00
微凉 bfec01e837 💚 change build platform 2021-11-04 13:23:17 +08:00
微凉 a7ecb7beb8 🐛 meta can't delete 2021-11-03 23:30:44 +08:00
微凉 c466672626 💚 fix action name 2021-11-03 21:28:18 +08:00
微凉 51811bf3db 👷 release 2021-11-03 20:58:19 +08:00
微凉 7c45e48f3f 💚 fix Permission denied 2021-11-03 20:02:51 +08:00
微凉 d73854afb5 💚 fix every step must define a uses or run key 2021-11-03 19:58:48 +08:00
微凉 e935d09cfe 👷 build action 2021-11-03 19:56:16 +08:00
微凉 1f91e26730 build 2021-11-03 19:51:19 +08:00
微凉 d78b64ee3c account index 2021-11-03 10:37:33 +08:00
微凉 04e89754f7 support onedrive 2021-11-02 23:53:05 +08:00
微凉 510292c8b0 meta api 2021-11-02 19:25:54 +08:00
微凉 8b81aeb5a1 ignore record not found 2021-11-01 23:14:34 +08:00
微凉 5c2ce5b442 clear cache 2021-11-01 22:42:24 +08:00
微凉 6313939e8c preview api 2021-10-31 21:27:47 +08:00
微凉 a3f1553d40 link 2021-10-31 00:36:17 +08:00
微凉 47bea7cc38 🐛 ali proxy 2021-10-30 21:06:38 +08:00
微凉 eb358e6ede static files 2021-10-30 19:26:23 +08:00
微凉 2780efaea8 🐛 sql group 2021-10-30 17:34:34 +08:00
微凉 0324ea1fcb settings 2021-10-30 00:35:29 +08:00
微凉 57ad66b43a guide 2021-10-29 14:50:26 +08:00
微凉 f4969560d4 proxy 2021-10-29 00:02:02 +08:00
微凉 0a50fbd080 ali path and link 2021-10-28 22:50:09 +08:00
微凉 98f7dffed9 🔨 change path 2021-10-28 12:37:31 +08:00
微凉 55f683b12d bootstrap 2021-10-27 22:45:36 +08:00
微凉 9644cc98c3 cache 2021-10-27 18:59:03 +08:00
微凉 fb7e67724d basic structure 2021-10-26 22:28:37 +08:00
微凉 d68a4048da 🎉 initial commit 2021-10-25 18:53:59 +08:00
95 changed files with 11695 additions and 1854 deletions
-2
View File
@@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto
+34 -46
View File
@@ -2,70 +2,58 @@ name: build
on: on:
push: push:
branches: [ main ] branches: [ v2 ]
pull_request: pull_request:
branches: [ main ] branches: [ v2 ]
jobs: jobs:
build: build:
strategy: strategy:
matrix: matrix:
platform: [ubuntu-16.04] platform: [ubuntu-latest]
go-version: [1.15] go-version: [1.17]
name: Build name: Build
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Check out code into the Go module directory - name: Set up Node
uses: actions/setup-node@v2
with:
node-version: '16'
# - name: Setup docker
# uses: docker-practice/actions-setup-docker@master
- name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
with:
ref: v2
path: alist
- name: Get dependencies - name: Checkout web repo
uses: actions/checkout@v2
with:
repository: Xhofe/alist-web
ref: v2
path: alist-web
- name: Set up xgo
run: | run: |
sudo apt-get update docker pull techknowlogick/xgo:latest
sudo apt-get -y install gcc-mingw-w64-x86-64 go install src.techknowlogick.com/xgo@latest
sudo apt-get -y install gcc-arm-linux-gnueabihf libc6-dev-armhf-cross sudo apt install upx
sudo apt-get -y install gcc-aarch64-linux-gnu libc6-dev-arm64-cross
go get -v -t -d ./... - name: Build
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Build linux
run: | run: |
CC=gcc CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o alist_linux_amd64 alist.go mv alist/build.sh .
CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -o alist_linux_arm64 alist.go bash build.sh
CC=arm-linux-gnueabihf-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -o alist_linux_arm alist.go
- name: Build windows - name: Upload artifact
run: |
CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -o alist_windows_amd64.exe alist.go
- name: Upload artifacts linux_amd64
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: alist_linux_amd64 name: artifact
path: alist_linux_amd64 path: alist/build
- name: Upload artifacts linux_arm64
uses: actions/upload-artifact@v2
with:
name: alist_linux_arm64
path: alist_linux_arm64
- name: Upload artifacts linux_arm
uses: actions/upload-artifact@v2
with:
name: alist_linux_arm
path: alist_linux_arm
- name: Upload artifacts windows_amd64
uses: actions/upload-artifact@v2
with:
name: alist_windows_amd64
path: alist_windows_amd64.exe
+48
View File
@@ -0,0 +1,48 @@
name: docker
on:
push:
branches:
- 'v2'
tags:
- 'v*'
pull_request:
branches:
- 'v2'
jobs:
docker:
name: Docker
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: xhofe/alist
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Build web
run: bash build.sh web
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: xhofe
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
+42 -90
View File
@@ -1,4 +1,5 @@
name: release name: release
on: on:
push: push:
tags: tags:
@@ -8,9 +9,9 @@ jobs:
release: release:
strategy: strategy:
matrix: matrix:
platform: [ ubuntu-16.04 ] platform: [ubuntu-latest]
go-version: [ 1.15 ] go-version: [1.17]
name: Build name: Release
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- name: Setup Go - name: Setup Go
@@ -18,100 +19,51 @@ jobs:
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- name: Get version # - name: Setup docker
id: get_version # uses: docker-practice/actions-setup-docker@master
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- name: Check out code into the Go module directory - name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
with:
ref: v2
path: alist
persist-credentials: false
fetch-depth: 0
- name: Get dependencies - name: Checkout web repo
uses: actions/checkout@v2
with:
repository: Xhofe/alist-web
ref: v2
path: alist-web
persist-credentials: false
fetch-depth: 0
- name: Set up xgo
run: | run: |
sudo apt-get update docker pull techknowlogick/xgo:latest
sudo apt-get -y install gcc-mingw-w64-x86-64 go install src.techknowlogick.com/xgo@latest
sudo apt-get -y install gcc-arm-linux-gnueabihf libc6-dev-armhf-cross sudo apt install upx
sudo apt-get -y install gcc-aarch64-linux-gnu libc6-dev-arm64-cross
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Build linux - name: Build
run: | run: |
CC=gcc CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o linux_amd64/alist alist.go mv alist/build.sh .
CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -o linux_arm64/alist alist.go bash build.sh release
CC=arm-linux-gnueabihf-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm go build -o linux_arm/alist alist.go
- name: Build windows - name: Upload asserts files
run: | uses: ad-m/github-push-action@master
CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -o windows_amd64/alist.exe alist.go
- name: compress
run: |
tar -czvf alist_linux_amd64.tar.gz linux_amd64/alist conf.yml.example
tar -czvf alist_linux_arm64.tar.gz linux_arm64/alist conf.yml.example
tar -czvf alist_linux_arm.tar.gz linux_arm/alist conf.yml.example
zip alist_windows_amd64.zip windows_amd64/alist.exe conf.yml.example
- name: Build Changelog
id: github_release
uses: mikepenz/release-changelog-builder-action@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ github.ref }} github_token: ${{ secrets.MY_TOKEN }}
release_name: Release ${{ github.ref }} branch: cdn
body: ${{steps.github_release.outputs.changelog}} directory: alist-web
draft: false repository: Xhofe/alist-web
prerelease: false
- name: Upload alist_linux_amd64 - name: Release
id: upload-release-linux-amd64 uses: softprops/action-gh-release@v1
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} files: alist/build/compress/*
asset_path: alist_linux_amd64.tar.gz
asset_name: alist_${{ steps.get_version.outputs.VERSION }}_linux_amd64.tar.gz
asset_content_type: application/gzip
- name: Upload alist_linux_arm64
id: upload-release-linux-arm64
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: alist_linux_arm64.tar.gz
asset_name: alist_${{ steps.get_version.outputs.VERSION }}_linux_arm64.tar.gz
asset_content_type: application/gzip
- name: Upload alist_linux_arm
id: upload-release-linux-arm
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: alist_linux_arm.tar.gz
asset_name: alist_${{ steps.get_version.outputs.VERSION }}_linux_arm.tar.gz
asset_content_type: application/gzip
- name: Upload alist_windows_amd64
id: upload-release-windows-amd64
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: alist_windows_amd64.zip
asset_name: alist_${{ steps.get_version.outputs.VERSION }}_windows_amd64.zip
asset_content_type: application/zip
+4 -1
View File
@@ -20,6 +20,9 @@ dist/
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
*.yml
bin/* bin/*
alist alist
*.json
public/index.html
public/assets/
data/
+14
View File
@@ -0,0 +1,14 @@
FROM alpine:edge as builder
LABEL stage=go-builder
WORKDIR /app/
COPY ./ ./
RUN apk add --no-cache bash git go gcc musl-dev; \
sh build.sh docker
FROM alpine:edge
LABEL MAINTAINER="i@nn.ci"
VOLUME /opt/alist/data/
WORKDIR /opt/alist/
COPY --from=builder /app/bin/alist ./
EXPOSE 5244
CMD [ "./alist" ]
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 微凉
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.
+13 -31
View File
@@ -1,6 +1,4 @@
<p align="center"> <h2 align="center">Alist</h2>
<img src="https://img.oez.cc/2020/12/24/1fb16bc25a4f6.png" alt="AList Logo" width=200/>
</p>
<p align="center"> <p align="center">
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="Release version"></a> <a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="Release version"></a>
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a> <a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
@@ -15,7 +13,7 @@
### 这是什么? ### 这是什么?
一款阿里云盘的目录文件列表程序,后端基于`golang`最好的`http`框架`gin`,前端使用`vue``ant design` 一款支持多种存储的目录文件列表程序,后端基于`gin`,前端使用`react`
### 前端项目地址 ### 前端项目地址
@@ -23,41 +21,25 @@
### 演示地址 ### 演示地址
- https://alist.nn.ci (稳定版本) - https://alist.nn.ci
- https://alist.now.sh (开发版本)
- https://alist-plyr.now.sh (plyr分支版本)
### 预览 ### 预览
<a href="https://alist.nn.ci/"><img src="https://img.oez.cc/2020/12/24/d81d2dab3e5f0.png"></a> <a href="https://alist.nn.ci/"><img src="https://store.heytapimage.com/cdo-portal/feedback/202111/03/695ef77854a144e928518efde38db97a.png"></a>
### 支持的功能 ### 支持的存储
- 自动刷新`token``refresh_token`自动更新,失效时间未知,本人使用过程中没有失效过。 - 本地存储
- 图片、视频、音频预览 - 阿里云盘
- 目录加密 - Onedrive/世纪互联
- `Readme`渲染 - 天翼云盘
- 自定义根目录 - GoogleDrive
- 文件直链下载 - 123pan
- - ...
#### TODO
- [x] 排序
- [x] 文件预览
- [x] 图片
- [x] 视频
- [x] 音频
- [x] `Readme`渲染
- [x] 密码加密
- [ ] 搜索与翻页
- [x] 文件直链
- [ ] 路径优化
- [x] 缓存
### 如何使用 ### 如何使用
- https://www.nn.ci/archives/alist.html - https://alist-doc.nn.ci/
### License ### License
-45
View File
@@ -1,45 +0,0 @@
package alidrive
import (
"encoding/json"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
)
// refresh access_token token by refresh_token
func RefreshToken(drive *conf.Drive) bool {
log.Infof("刷新[%s]token...", drive.Name)
url := "https://websv.aliyundrive.com/token/refresh"
req := RefreshTokenReq{RefreshToken: drive.RefreshToken}
var token TokenResp
if body, err := DoPost(url, req, ""); err != nil {
log.Errorf("tokenLogin-doPost出错:%s", err.Error())
return false
} else {
if err = json.Unmarshal(body, &token); err != nil {
log.Errorf("解析json[%s]出错:%s", string(body), err.Error())
log.Errorf("此处json解析失败应该是[%s]refresh_token失效", drive.Name)
return false
}
}
//刷新成功 更新token
drive.AccessToken = token.AccessToken
drive.RefreshToken = token.RefreshToken
return true
}
func RefreshTokenAll() string {
log.Infof("刷新所有token...")
res := ""
for i, drive := range conf.Conf.AliDrive.Drives {
if !RefreshToken(&conf.Conf.AliDrive.Drives[i]) {
res = res + drive.Name + ","
}
}
utils.WriteToYml(conf.ConfigFile, conf.Conf)
if res != "" {
return res[:len(res)-1]
}
return ""
}
-67
View File
@@ -1,67 +0,0 @@
package alidrive
// list request bean
type ListReq struct {
DriveId string `json:"drive_id"`
Fields string `json:"fields"`
ImageThumbnailProcess string `json:"image_thumbnail_process"`
ImageUrlProcess string `json:"image_url_process"`
Limit int `json:"limit"`
Marker string `json:"marker"`
OrderBy string `json:"order_by"`
OrderDirection string `json:"order_direction"`
ParentFileId string `json:"parent_file_id"`
VideoThumbnailProcess string `json:"video_thumbnail_process"`
}
// get request bean
type GetReq struct {
DriveId string `json:"drive_id"`
FileId string `json:"file_id"`
ImageThumbnailProcess string `json:"image_thumbnail_process"`
VideoThumbnailProcess string `json:"video_thumbnail_process"`
}
// download request bean
type DownloadReq struct {
DriveId string `json:"drive_id"`
FileId string `json:"file_id"`
ExpireSec int `json:"expire_sec"`
FileName string `json:"file_name"`
}
// search request bean
type SearchReq struct {
DriveId string `json:"drive_id"`
ImageThumbnailProcess string `json:"image_thumbnail_process"`
ImageUrlProcess string `json:"image_url_process"`
Limit int `json:"limit"`
Marker string `json:"marker"`
OrderBy string `json:"order_by"` //"type ASC,updated_at DESC"
Query string `json:"query"` // "name match '测试文件'"
VideoThumbnailProcess string `json:"video_thumbnail_process"`
}
// token_login request bean
type TokenLoginReq struct {
Token string `json:"token"`
}
// get_token request bean
type GetTokenReq struct {
Code string `json:"code"`
}
// refresh_token request bean
type RefreshTokenReq struct {
RefreshToken string `json:"refresh_token"`
}
// office_preview_url request bean
type OfficePreviewUrlReq struct {
AccessToken string `json:"access_token"`
DriveId string `json:"drive_id"`
FileId string `json:"file_id"`
}
-183
View File
@@ -1,183 +0,0 @@
package alidrive
import (
"bytes"
"encoding/json"
"fmt"
"github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"strings"
"time"
)
// get file
func GetFile(fileId string, drive *conf.Drive) (*File, error) {
url := conf.Conf.AliDrive.ApiUrl + "/file/get"
req := GetReq{
DriveId: drive.DefaultDriveId,
FileId: fileId,
ImageThumbnailProcess: conf.ImageThumbnailProcess,
VideoThumbnailProcess: conf.VideoThumbnailProcess,
}
var resp File
if err := BodyToJson(url, req, &resp, drive); err != nil {
return nil, err
}
return &resp, nil
}
// get download_url
func GetDownLoadUrl(fileId string, drive *conf.Drive) (*DownloadResp, error) {
url := conf.Conf.AliDrive.ApiUrl + "/file/get_download_url"
req := DownloadReq{
DriveId: drive.DefaultDriveId,
FileId: fileId,
ExpireSec: 14400,
}
var resp DownloadResp
if err := BodyToJson(url, req, &resp, drive); err != nil {
return nil, err
}
return &resp, nil
}
// search by keyword
func Search(key string, limit int, marker string, drive *conf.Drive) (*Files, error) {
url := conf.Conf.AliDrive.ApiUrl + "/file/search"
req := SearchReq{
DriveId: drive.DefaultDriveId,
ImageThumbnailProcess: conf.ImageThumbnailProcess,
ImageUrlProcess: conf.ImageUrlProcess,
Limit: limit,
Marker: marker,
OrderBy: conf.OrderSearch,
Query: fmt.Sprintf("name match '%s'", key),
VideoThumbnailProcess: conf.VideoThumbnailProcess,
}
var resp Files
if err := BodyToJson(url, req, &resp, drive); err != nil {
return nil, err
}
return &resp, nil
}
// get root folder
func GetRoot(limit int, marker string, orderBy string, orderDirection string, drive *conf.Drive) (*Files, error) {
return GetList(drive.RootFolder, limit, marker, orderBy, orderDirection, drive)
}
// get folder list by file_id
func GetList(parent string, limit int, marker string, orderBy string, orderDirection string, drive *conf.Drive) (*Files, error) {
url := conf.Conf.AliDrive.ApiUrl + "/file/list"
req := ListReq{
DriveId: drive.DefaultDriveId,
Fields: "*",
ImageThumbnailProcess: conf.ImageThumbnailProcess,
ImageUrlProcess: conf.ImageUrlProcess,
Limit: limit,
Marker: marker,
OrderBy: orderBy,
OrderDirection: orderDirection,
ParentFileId: parent,
VideoThumbnailProcess: conf.VideoThumbnailProcess,
}
var resp Files
if err := BodyToJson(url, req, &resp, drive); err != nil {
return nil, err
}
return &resp, nil
}
// get user info
func GetUserInfo(drive *conf.Drive) (*UserInfo, error) {
url := conf.Conf.AliDrive.ApiUrl + "/user/get"
var resp UserInfo
if err := BodyToJson(url, map[string]interface{}{}, &resp, drive); err != nil {
return nil, err
}
return &resp, nil
}
// get office preview url and token
func GetOfficePreviewUrl(fileId string, drive *conf.Drive) (*OfficePreviewUrlResp, error) {
url := conf.Conf.AliDrive.ApiUrl + "/file/get_office_preview_url"
req := OfficePreviewUrlReq{
AccessToken: drive.AccessToken,
DriveId: drive.DefaultDriveId,
FileId: fileId,
}
var resp OfficePreviewUrlResp
if err := BodyToJson(url, req, &resp, drive); err != nil {
return nil, err
}
return &resp, nil
}
// convert body to json
func BodyToJson(url string, req interface{}, resp RespHandle, drive *conf.Drive) error {
if body, err := DoPost(url, req, drive.AccessToken); err != nil {
log.Errorf("doPost出错:%s", err.Error())
return err
} else {
if err = json.Unmarshal(body, &resp); err != nil {
log.Errorf("解析json[%s]出错:%s", string(body), err.Error())
return err
}
}
if resp.IsAvailable() {
return nil
}
if resp.GetCode() == conf.AccessTokenInvalid {
resp.SetCode("")
if RefreshToken(drive) {
return BodyToJson(url, req, resp, drive)
}
}
return fmt.Errorf(resp.GetMessage())
}
// do post request
func DoPost(url string, request interface{}, auth string) (body []byte, err error) {
var (
resp *http.Response
)
requestBody := new(bytes.Buffer)
err = json.NewEncoder(requestBody).Encode(request)
if err != nil {
log.Errorf("创建requestBody出错:%s", err.Error())
}
req, err := http.NewRequest("POST", url, requestBody)
log.Debugf("do_post_req:%+v", req)
if err != nil {
log.Errorf("创建request出错:%s", err.Error())
return
}
if auth != "" {
req.Header.Set("authorization", conf.Bearer+auth)
}
req.Header.Add("content-type", "application/json")
req.Header.Add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
req.Header.Add("origin", "https://aliyundrive.com")
req.Header.Add("accept", "*/*")
req.Header.Add("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")
req.Header.Add("Connection", "keep-alive")
for retryCount := 3; retryCount >= 0; retryCount-- {
if resp, err = conf.Client.Do(req); err != nil && strings.Contains(err.Error(), "timeout") {
<-time.After(time.Second)
} else {
break
}
}
if err != nil {
log.Errorf("请求阿里云盘api时出错:%s", err.Error())
return
}
if body, err = ioutil.ReadAll(resp.Body); err != nil {
log.Errorf("读取api返回内容失败")
}
log.Debugf("请求返回信息:%s", string(body))
return
}
-184
View File
@@ -1,184 +0,0 @@
package alidrive
import (
log "github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"strings"
"time"
)
// response bean methods
type RespHandle interface {
IsAvailable() bool // check available
GetCode() string // get err code
GetMessage() string // get err message
SetCode(code string) // set err code
}
// common response bean
type RespError struct {
Code string `json:"code"`
Message string `json:"message"`
}
func (resp *RespError) IsAvailable() bool {
return resp.Code == ""
}
func (resp *RespError) GetCode() string {
return resp.Code
}
func (resp *RespError) GetMessage() string {
return resp.Message
}
func (resp *RespError) SetCode(code string) {
resp.Code = code
}
// user_info response bean
type UserInfo struct {
RespError
DomainId string `json:"domain_id"`
UserId string `json:"user_id"`
Avatar string `json:"avatar"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
Email string `json:"email"`
NickName string `json:"nick_name"`
Phone string `json:"phone"`
Role string `json:"role"`
Status string `json:"status"`
UserName string `json:"user_name"`
Description string `json:"description"`
DefaultDriveId string `json:"default_drive_id"`
UserData map[string]interface{} `json:"user_data"`
}
// folder files response bean
type Files struct {
RespError
Items []File `json:"items"`
NextMarker string `json:"next_marker"`
Readme string `json:"readme"` // Deprecated
Paths []Path `json:"paths"`
}
// path bean
type Path struct {
Name string `json:"name"`
FileId string `json:"file_id"`
}
// file response bean
type File struct {
RespError
DriveId string `json:"drive_id"`
CreatedAt *time.Time `json:"created_at"`
DomainId string `json:"domain_id"`
EncryptMode string `json:"encrypt_mode"`
FileExtension string `json:"file_extension"`
FileId string `json:"file_id"`
Hidden bool `json:"hidden"`
Name string `json:"name"`
ParentFileId string `json:"parent_file_id"`
Starred bool `json:"starred"`
Status string `json:"status"`
Type string `json:"type"`
UpdatedAt *time.Time `json:"updated_at"`
// 文件多出部分
Category string `json:"category"`
ContentHash string `json:"content_hash"`
ContentHashName string `json:"content_hash_name"`
ContentType string `json:"content_type"`
Crc64Hash string `json:"crc_64_hash"`
DownloadUrl string `json:"download_url"`
PunishFlag int64 `json:"punish_flag"`
Size int64 `json:"size"`
Thumbnail string `json:"thumbnail"`
Url string `json:"url"`
ImageMediaMetadata map[string]interface{} `json:"image_media_metadata"`
Paths []Path `json:"paths"`
}
type DownloadResp struct {
RespError
Expiration string `json:"expiration"`
Method string `json:"method"`
Size int64 `json:"size"`
Url string `json:"url"`
//RateLimit struct{
// PartSize int `json:"part_size"`
// PartSpeed int `json:"part_speed"`
//} `json:"rate_limit"`//rate limit
}
// token_login response bean
type TokenLoginResp struct {
RespError
Goto string `json:"goto"`
}
// token response bean
type TokenResp struct {
RespError
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"`
UserInfo
DefaultSboxDriveId string `json:"default_sbox_drive_id"`
ExpireTime *time.Time `json:"expire_time"`
State string `json:"state"`
ExistLink []interface{} `json:"exist_link"`
NeedLink bool `json:"need_link"`
PinSetup bool `json:"pin_setup"`
IsFirstLogin bool `json:"is_first_login"`
NeedRpVerify bool `json:"need_rp_verify"`
DeviceId string `json:"device_id"`
}
// office_preview_url response bean
type OfficePreviewUrlResp struct {
RespError
PreviewUrl string `json:"preview_url"`
AccessToken string `json:"access_token"`
}
// check password
func HasPassword(files *Files) string {
fileList := files.Items
for i, file := range fileList {
if strings.HasPrefix(file.Name, ".password-") {
files.Items = fileList[:i+copy(fileList[i:], fileList[i+1:])]
return file.Name[10:]
}
}
return ""
}
// Deprecated: check readme, implemented by the front end now
func HasReadme(files *Files) string {
fileList := files.Items
for _, file := range fileList {
if file.Name == "Readme.md" {
resp, err := http.Get(file.Url)
if err != nil {
log.Errorf("Get Readme出错:%s", err.Error())
return ""
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Errorf("读取 Readme出错:%s", err.Error())
return ""
}
return string(data)
}
}
return ""
}
+57 -4
View File
@@ -1,8 +1,61 @@
package main package main
import "github.com/Xhofe/alist/bootstrap" import (
"flag"
"fmt"
"github.com/Xhofe/alist/bootstrap"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
// main function func init() {
func main() { flag.StringVar(&conf.ConfigFile, "conf", "data/config.json", "config file")
bootstrap.Run() flag.BoolVar(&conf.Debug, "debug", false, "start with debug mode")
flag.BoolVar(&conf.Version, "version", false, "print version info")
flag.BoolVar(&conf.Password, "password", false, "print current password")
flag.Parse()
}
func Init() bool {
bootstrap.InitLog()
bootstrap.InitConf()
bootstrap.InitCron()
bootstrap.InitModel()
if conf.Password {
pass, err := model.GetSettingByKey("password")
if err != nil {
log.Errorf(err.Error())
return false
}
log.Infof("current password: %s", pass.Value)
return false
}
bootstrap.InitSettings()
bootstrap.InitAccounts()
bootstrap.InitCache()
return true
}
func main() {
if conf.Version {
fmt.Printf("Built At: %s\nGo Version: %s\nAuthor: %s\nCommit ID: %s\nVersion: %s\n", conf.BuiltAt, conf.GoVersion, conf.GitAuthor, conf.GitCommit, conf.GitTag)
return
}
if !Init() {
return
}
if !conf.Debug {
gin.SetMode(gin.ReleaseMode)
}
r := gin.Default()
server.InitApiRouter(r)
base := fmt.Sprintf("%s:%d", conf.Conf.Address, conf.Conf.Port)
log.Infof("start server @ %s", base)
err := r.Run(base)
if err != nil {
log.Errorf("failed to start: %s", err.Error())
}
} }
+12
View File
@@ -0,0 +1,12 @@
package main
import (
"fmt"
"net/url"
"testing"
)
func TestUrl(t *testing.T) {
s,_ := url.QueryUnescape("/ali/%E7%8C%AA%E5%A4%B4%E7%9A%84%E6%96%87%E4%BB%B6%5B%E5%98%BF%E5%98%BF%5D/%E9%82%B9%E9%82%B9%E7%9A%84%E6%96%87%E4%BB%B6/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8/v2-e8f266ba17ae387eefed1cb22b2b5e4e_r.jpg")
fmt.Print(s)
}
+30
View File
@@ -0,0 +1,30 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
log "github.com/sirupsen/logrus"
)
func InitAccounts() {
log.Infof("init accounts...")
var accounts []model.Account
if err := conf.DB.Find(&accounts).Error; err != nil {
log.Fatalf("failed sync init accounts")
}
for i, account := range accounts {
model.RegisterAccount(account)
driver, ok := drivers.GetDriver(account.Type)
if !ok {
log.Errorf("no [%s] driver", account.Type)
} else {
err := driver.Save(&accounts[i], nil)
if err != nil {
log.Errorf("init account [%s] error:[%s]", account.Name, err.Error())
} else {
log.Infof("success init account: %s, type: %s", account.Name, account.Type)
}
}
}
}
-33
View File
@@ -1,33 +0,0 @@
package bootstrap
import (
"github.com/Xhofe/alist/alidrive"
"github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
)
// init aliyun drive
func InitAliDrive() bool {
log.Infof("初始化阿里云盘...")
//首先token_login
res := alidrive.RefreshTokenAll()
if res != "" {
log.Errorf("盘[%s]refresh_token失效,请检查", res)
}
log.Debugf("config:%+v", conf.Conf)
for i, _ := range conf.Conf.AliDrive.Drives {
InitDriveId(&conf.Conf.AliDrive.Drives[i])
}
return true
}
func InitDriveId(drive *conf.Drive) bool {
user, err := alidrive.GetUserInfo(drive)
if err != nil {
log.Errorf("初始化盘[%s]失败:%s", drive.Name, err.Error())
return false
}
drive.DefaultDriveId = user.DefaultDriveId
log.Infof("初始化盘[%s]成功:%+v", drive.Name, user)
return true
}
+18
View File
@@ -0,0 +1,18 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/eko/gocache/v2/cache"
"github.com/eko/gocache/v2/store"
goCache "github.com/patrickmn/go-cache"
log "github.com/sirupsen/logrus"
"time"
)
// InitCache init cache
func InitCache() {
log.Infof("init cache...")
goCacheClient := goCache.New(60*time.Minute, 120*time.Minute)
goCacheStore := store.NewGoCache(goCacheClient, nil)
conf.Cache = cache.New(goCacheStore)
}
-13
View File
@@ -1,13 +0,0 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
"net/http"
)
// init request client
func InitClient() {
log.Infof("初始化client...")
conf.Client = &http.Client{}
}
-82
View File
@@ -1,82 +0,0 @@
package bootstrap
import (
"flag"
"fmt"
"github.com/Xhofe/alist/conf"
serv "github.com/Xhofe/alist/server"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func init() {
flag.BoolVar(&conf.Debug, "debug", false, "use debug mode")
flag.BoolVar(&conf.Help, "help", false, "show usage help")
flag.BoolVar(&conf.Version, "version", false, "show version info")
flag.StringVar(&conf.ConfigFile, "conf", "conf.yml", "config file")
flag.BoolVar(&conf.SkipUpdate, "skip-update", false, "skip update")
}
// bootstrap run
func Run() {
flag.Parse()
if conf.Help {
flag.Usage()
return
}
if conf.Version {
fmt.Println("Current version:" + conf.VERSION)
return
}
start()
}
// print asc
func printASC() {
log.Info(`
________ ___ ___ ________ _________
|\ __ \|\ \ |\ \|\ ____\|\___ ___\
\ \ \|\ \ \ \ \ \ \ \ \___|\|___ \ \_|
\ \ __ \ \ \ \ \ \ \_____ \ \ \ \
\ \ \ \ \ \ \____\ \ \|____|\ \ \ \ \
\ \__\ \__\ \_______\ \__\____\_\ \ \ \__\
\|__|\|__|\|_______|\|__|\_________\ \|__|
\|_________|
`)
}
// start server
func start() {
InitLog()
printASC()
if !conf.SkipUpdate {
CheckUpdate()
}
if !ReadConf(conf.ConfigFile) {
log.Errorf("读取配置文件时出现错误,启动失败.")
return
}
InitClient()
if !InitAliDrive() {
log.Errorf("初始化阿里云盘出现错误,启动失败.")
return
}
if !InitModel() {
log.Errorf("初始化数据库出现错误,启动失败.")
return
}
InitCron()
server()
}
// start http server
func server() {
baseServer := "0.0.0.0:" + conf.Conf.Server.Port
r := gin.Default()
serv.InitRouter(r)
log.Infof("Starting server @ %s", baseServer)
err := r.Run(baseServer)
if err != nil {
log.Errorf("Server failed start:%s", err.Error())
}
}
+36
View File
@@ -0,0 +1,36 @@
package bootstrap
import (
"encoding/json"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io/ioutil"
)
// InitConf init config
func InitConf() {
log.Infof("reading config file: %s", conf.ConfigFile)
if !utils.Exists(conf.ConfigFile) {
log.Infof("config file not exists, creating default config file")
_, err := utils.CreatNestedFile(conf.ConfigFile)
if err != nil {
log.Fatalf("failed to create config file")
}
conf.Conf = conf.DefaultConfig()
if !utils.WriteToJson(conf.ConfigFile, conf.Conf) {
log.Fatalf("failed to create default config file")
}
return
}
config, err := ioutil.ReadFile(conf.ConfigFile)
if err != nil {
log.Fatalf("reading config file error:%s", err.Error())
}
conf.Conf = new(conf.Config)
err = json.Unmarshal(config, conf.Conf)
if err != nil {
log.Fatalf("load config error: %s", err.Error())
}
log.Debugf("config:%+v", conf.Conf)
}
-33
View File
@@ -1,33 +0,0 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"io/ioutil"
"strings"
)
// read config file
func ReadConf(config string) bool {
log.Infof("读取配置文件...")
if !utils.Exists(config) {
log.Infof("找不到配置文件:%s", config)
return false
}
confFile, err := ioutil.ReadFile(config)
if err != nil {
log.Errorf("读取配置文件时发生错误:%s", err.Error())
return false
}
err = yaml.Unmarshal(confFile, conf.Conf)
if err != nil {
log.Errorf("加载配置文件时发生错误:%s", err.Error())
return false
}
log.Debugf("config:%+v", conf.Conf)
conf.Conf.Info.Roots = utils.GetNames()
conf.Origins = strings.Split(conf.Conf.Server.SiteUrl, ",")
return true
}
+5 -16
View File
@@ -1,25 +1,14 @@
package bootstrap package bootstrap
import ( import (
"github.com/Xhofe/alist/alidrive" "github.com/Xhofe/alist/conf"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
var Cron *cron.Cron // InitCron init cron
// refresh token func for cron
func refreshToken() {
alidrive.RefreshTokenAll()
}
// init cron jobs
func InitCron() { func InitCron() {
log.Infof("初始化定时任务:刷新token") log.Infof("init cron...")
Cron = cron.New() conf.Cron = cron.New()
_, err := Cron.AddFunc("@every 2h", refreshToken) conf.Cron.Start()
if err != nil {
log.Errorf("添加启动任务失败:%s", err.Error())
}
Cron.Start()
} }
+10
View File
@@ -0,0 +1,10 @@
package bootstrap
import (
_ "github.com/Xhofe/alist/drivers/123pan"
_ "github.com/Xhofe/alist/drivers/189cloud"
_ "github.com/Xhofe/alist/drivers/alidrive"
_ "github.com/Xhofe/alist/drivers/googledrive"
_ "github.com/Xhofe/alist/drivers/native"
_ "github.com/Xhofe/alist/drivers/onedrive"
)
+3 -4
View File
@@ -2,18 +2,17 @@ package bootstrap
import ( import (
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// init logrus // initLog init log
func InitLog() { func InitLog() {
if conf.Debug { if conf.Debug {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
} else { log.SetReportCaller(true)
gin.SetMode(gin.ReleaseMode)
} }
log.SetFormatter(&log.TextFormatter{ log.SetFormatter(&log.TextFormatter{
//DisableColors: true,
ForceColors: true, ForceColors: true,
EnvironmentOverrideColors: true, EnvironmentOverrideColors: true,
TimestampFormat: "2006-01-02 15:04:05", TimestampFormat: "2006-01-02 15:04:05",
+49 -43
View File
@@ -3,72 +3,78 @@ package bootstrap
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/server/models" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema" "gorm.io/gorm/schema"
log2 "log"
"os"
"strings" "strings"
"time"
) )
func InitModel() bool { func InitModel() {
log.Infof("初始化数据库...") log.Infof("init model...")
dbConfig := conf.Conf.Database databaseConfig := conf.Conf.Database
switch dbConfig.Type { newLogger := logger.New(
log2.New(os.Stdout, "\r\n", log2.LstdFlags),
logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Silent,
IgnoreRecordNotFoundError: true,
Colorful: true,
},
)
gormConfig := &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: databaseConfig.TablePrefix,
},
Logger: newLogger,
}
switch databaseConfig.Type {
case "sqlite3": case "sqlite3":
{ {
if !(strings.HasSuffix(dbConfig.DBFile, ".db") && len(dbConfig.DBFile) > 3) { if !(strings.HasSuffix(databaseConfig.DBFile, ".db") && len(databaseConfig.DBFile) > 3) {
log.Errorf("db名称不正确.") log.Fatalf("db name error.")
return false
} }
needMigrate := !utils.Exists(dbConfig.DBFile) db, err := gorm.Open(sqlite.Open(databaseConfig.DBFile), gormConfig)
db, err := gorm.Open(sqlite.Open(dbConfig.DBFile), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: dbConfig.TablePrefix,
},
})
if err != nil { if err != nil {
log.Errorf("连接数据库出现错误:%s", err.Error()) log.Fatalf("failed to connect database:%s", err.Error())
return false
} }
conf.DB = db conf.DB = db
if needMigrate {
log.Infof("迁移数据库...")
err = conf.DB.AutoMigrate(&models.File{})
if err != nil {
log.Errorf("数据库迁移失败:%s", err.Error())
return false
}
//models.BuildTreeAll()
}
return true
} }
case "mysql": case "mysql":
{ {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Name) databaseConfig.User, databaseConfig.Password, databaseConfig.Host, databaseConfig.Port, databaseConfig.Name)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ db, err := gorm.Open(mysql.Open(dsn), gormConfig)
NamingStrategy: schema.NamingStrategy{
TablePrefix: dbConfig.TablePrefix,
},
})
if err != nil { if err != nil {
log.Errorf("连接数据库出现错误:%s", err.Error()) log.Fatalf("failed to connect database:%s", err.Error())
return false
} }
conf.DB = db conf.DB = db
log.Infof("迁移数据库...")
err = conf.DB.AutoMigrate(&models.File{})
if err != nil {
log.Errorf("数据库迁移失败:%s", err.Error())
return false
} }
return true case "postgres":
{
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai",
databaseConfig.Host, databaseConfig.User, databaseConfig.Password, databaseConfig.Name, databaseConfig.Port)
db, err := gorm.Open(postgres.Open(dsn), gormConfig)
if err != nil {
log.Errorf("failed to connect database:%s", err.Error())
}
conf.DB = db
} }
default: default:
log.Errorf("不支持的数据库类型:%s", dbConfig.Type) log.Fatalf("not supported database type: %s", databaseConfig.Type)
return false }
log.Infof("auto migrate model...")
err := conf.DB.AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{})
if err != nil {
log.Fatalf("failed to auto migrate")
} }
} }
+169
View File
@@ -0,0 +1,169 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
)
func InitSettings() {
log.Infof("init settings...")
version := model.SettingItem{
Key: "version",
Value: conf.GitTag,
Description: "version",
Type: "string",
Group: model.CONST,
}
_ = model.SaveSetting(version)
settings := []model.SettingItem{
{
Key: "title",
Value: "Alist",
Description: "title",
Type: "string",
Group: model.PUBLIC,
},
{
Key: "password",
Value: "alist",
Description: "password",
Type: "string",
Group: model.PRIVATE,
},
{
Key: "logo",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
Description: "logo",
Type: "string",
Group: model.PUBLIC,
},
{
Key: "favicon",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
Description: "favicon",
Type: "string",
Group: model.PUBLIC,
},
{
Key: "icon color",
Value: "teal.300",
Description: "icon's color",
Type: "string",
Group: model.PUBLIC,
},
{
Key: "text types",
Value: "txt,htm,html,xml,java,properties,sql,js,md,json,conf,ini,vue,php,py,bat,gitignore,yml,go,sh,c,cpp,h,hpp,tsx",
Type: "string",
Description: "text type extensions",
},
{
Key: "hide readme file",
Value: "true",
Type: "bool",
Description: "hide readme file? ",
},
{
Key: "music cover",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
Description: "music cover image",
Type: "string",
Group: model.PUBLIC,
},
{
Key: "site beian",
Description: "chinese beian info",
Type: "string",
Group: model.PUBLIC,
},
{
Key: "home readme url",
Description: "when have multiple, the readme file to show",
Type: "string",
Group: model.PUBLIC,
},
{
Key: "markdown theme",
Value: "vuepress",
Description: "default | github | vuepress",
Group: model.PUBLIC,
Type: "select",
Values: "default,github,vuepress",
},
{
Key: "autoplay video",
Value: "false",
Type: "bool",
Group: model.PUBLIC,
},
{
Key: "autoplay audio",
Value: "false",
Type: "bool",
Group: model.PUBLIC,
},
{
Key: "check parent folder",
Value: "false",
Type: "bool",
Description: "check parent folder password",
Group: model.PRIVATE,
},
{
Key: "customize style",
Value: "",
Type: "text",
Description: "customize style, don't need add <style></style>",
Group: model.PRIVATE,
},
{
Key: "customize script",
Value: "",
Type: "text",
Description: "customize script, don't need add <script></script>",
Group: model.PRIVATE,
},
{
Key: "animation",
Value: "true",
Type: "bool",
Description: "when there are a lot of files, the animation will freeze when opening",
Group: model.PUBLIC,
},
{
Key: "check down link",
Value: "false",
Type: "bool",
Description: "check down link password, your link will be 'https://alist.com/d/filename?pw=xxx'",
Group: model.PUBLIC,
},
{
Key: "WebDAV username",
Value: "alist",
Description: "WebDAV username",
Type: "string",
Group: model.PRIVATE,
},
{
Key: "WebDAV password",
Value: "alist",
Description: "WebDAV password",
Type: "string",
Group: model.PRIVATE,
},
}
for _, v := range settings {
_, err := model.GetSettingByKey(v.Key)
if err == gorm.ErrRecordNotFound {
err = model.SaveSetting(v)
if err != nil {
log.Fatalf("failed write setting: %s", err.Error())
}
}
}
model.LoadSettings()
}
-47
View File
@@ -1,47 +0,0 @@
package bootstrap
import (
"encoding/json"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
)
// github release response bean
type GithubRelease struct {
TagName string `json:"tag_name"`
HtmlUrl string `json:"html_url"`
Body string `json:"body"`
}
// check update
func CheckUpdate() {
log.Infof("检查更新...")
url := "https://api.github.com/repos/Xhofe/alist/releases/latest"
resp, err := http.Get(url)
if err != nil {
log.Warnf("检查更新失败:%s", err.Error())
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Warnf("读取更新内容失败:%s", err.Error())
return
}
var release GithubRelease
err = json.Unmarshal(body, &release)
if err != nil {
log.Warnf("解析更新失败:%s", err.Error())
return
}
lasted := release.TagName[1:]
now := conf.VERSION[1:]
if utils.VersionCompare(lasted, now) != 1 {
log.Infof("当前已是最新版本:%s", conf.VERSION)
} else {
log.Infof("发现新版本:%s", release.TagName)
log.Infof("请至'%s'获取更新.", release.HtmlUrl)
}
}
+106
View File
@@ -0,0 +1,106 @@
#!/bin/bash
if [ "$1" == "web" ]; then
git clone https://github.com/Xhofe/alist-web.git
cd alist-web || exit
yarn
yarn build
mv dist/* ../public
cd ..
exit 0
fi
go env -w GOPROXY=https://goproxy.cn,https://mirrors.aliyun.com/goproxy/,https://goproxy.io,direct
if [ "$1" == "docker" ]; then
appName="alist"
builtAt="$(date +'%F %T %z')"
goVersion=$(go version | sed 's/go version //')
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
gitCommit=$(git log --pretty=format:"%h" -1)
gitTag=$(git describe --long --tags --dirty --always)
ldflags="\
-w -s \
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
-X 'github.com/Xhofe/alist/conf.GoVersion=$goVersion' \
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
"
go build -o ./bin/alist -ldflags="$ldflags" alist.go
exit 0
fi
cd alist-web || exit
webCommit=$(git log --pretty=format:"%h" -1)
echo "web commit id: $webCommit"
yarn
if [ "$1" == "release" ]; then
yarn build --base="https://cdn.jsdelivr.net/gh/Xhofe/alist-web@cdn/v2/$webCommit"
mv dist/assets ..
else
yarn build
fi
cd ..
cd alist
appName="alist"
builtAt="$(date +'%F %T %z')"
goVersion=$(go version | sed 's/go version //')
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
gitCommit=$(git log --pretty=format:"%h" -1)
gitTag=$(git describe --long --tags --dirty --always)
echo "build version: $gitTag"
ldflags="\
-w -s \
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
-X 'github.com/Xhofe/alist/conf.GoVersion=$goVersion' \
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
"
cp -R ../alist-web/dist/* public
if [ "$1" == "release" ]; then
xgo -out alist -ldflags="$ldflags" .
else
xgo -targets=linux/amd64,windows/amd64 -out alist -ldflags="$ldflags" .
fi
mkdir "build"
mv alist-* build
cd build || exit
upx -9 ./*
find . -type f -print0 | xargs -0 md5sum > md5.txt
cat md5.txt
# compress file (release)
if [ "$1" == "release" ]; then
mkdir compress
mv md5.txt compress
for i in `find . -type f -name "$appName-linux-*"`
do
tar -czvf compress/"$i".tar.gz "$i"
done
for i in `find . -type f -name "$appName-darwin-*"`
do
tar -czvf compress/"$i".tar.gz "$i"
done
for i in `find . -type f -name "$appName-windows-*"`
do
zip compress/$(echo $i | sed 's/\.[^.]*$//').zip "$i"
done
fi
cd ../..
if [ "$1" == "release" ]; then
cd alist-web
git checkout cdn
mkdir "v2/$webCommit"
mv ../assets/ v2/$webCommit
git add .
git config --local user.email "i@nn.ci"
git config --local user.name "Xhofe"
git commit --allow-empty -m "upload $webCommit assets files" -a
cd ..
fi
-34
View File
@@ -1,34 +0,0 @@
info:
title: AList #标题
logo: "" #网站logo 如果填写,则会替换掉默认的
footer_text: Xhofe's Blog #网页底部文字
footer_url: https://www.nn.ci #网页底部文字链接
music_img: https://img.oez.cc/2020/12/19/0f8b57866bdb5.gif #预览音乐文件时的图片
check_update: true #前端是否显示更新
script: #自定义脚本,可以是脚本的链接,也可以直接是脚本内容,如document.querySelector('body').style="background-image:url('https://api.mtyqx.cn/api/random.php');background-attachment:fixed"
autoplay: true #视频是否自动播放
preview:
text: [txt,htm,html,xml,java,properties,sql,js,md,json,conf,ini,vue,php,py,bat,gitignore,yml,go,sh,c,cpp,h,hpp] #要预览的文本文件的后缀,可以自行添加
server:
port: "5244"
search: true
static: dist
site_url: '*'
password: password #用于重建目录
ali_drive:
api_url: https://api.aliyundrive.com/v2
max_files_count: 3000
drives:
- refresh_token: xxx #refresh_token
root_folder: root #根目录的file_id
name: drive0 #盘名,多个盘不可重复
password: pass #该盘密码,空则不设密码,修改需要重建生效
hide: false #是否在主页隐藏该盘,不可全部隐藏,至少暴露一个
- refresh_token: xxx
root_folder: root
name: drive1
password: pass
hide: false
database:
type: sqlite3
dBFile: alist.db
+29 -50
View File
@@ -1,55 +1,34 @@
package conf package conf
type Drive struct { type Database struct {
AccessToken string `yaml:"-"` Type string `json:"type"`
RefreshToken string `yaml:"refresh_token"` User string `json:"user"`
RootFolder string `yaml:"root_folder"` //根目录id Password string `json:"password"`
Name string `yaml:"name"` Host string `json:"host"`
Password string `yaml:"password"` Port int `json:"port"`
Hide bool `yaml:"hide"` Name string `json:"name"`
DefaultDriveId string `yaml:"-"` TablePrefix string `json:"table_prefix"`
DBFile string `json:"db_file"`
}
type Config struct {
Address string `json:"address"`
Port int `json:"port"`
Database Database `json:"database"`
} }
// config struct func DefaultConfig() *Config {
type Config struct { return &Config{
Info struct { Address: "0.0.0.0",
Title string `yaml:"title" json:"title"` Port: 5244,
Roots []string `yaml:"-" json:"roots"` Database: Database{
Logo string `yaml:"logo" json:"logo"` Type: "sqlite3",
FooterText string `yaml:"footer_text" json:"footer_text"` User: "",
FooterUrl string `yaml:"footer_url" json:"footer_url"` Password: "",
MusicImg string `yaml:"music_img" json:"music_img"` Host: "",
CheckUpdate bool `yaml:"check_update" json:"check_update"` Port: 0,
Script string `yaml:"script" json:"script"` Name: "",
Autoplay bool `yaml:"autoplay" json:"autoplay"` TablePrefix: "x_",
Preview struct { DBFile: "data/data.db",
Url string `yaml:"url" json:"url"` },
PreProcess []string `yaml:"pre_process" json:"pre_process"` }
Extensions []string `yaml:"extensions" json:"extensions"`
Text []string `yaml:"text" json:"text"`
MaxSize int `yaml:"max_size" json:"max_size"`
} `yaml:"preview" json:"preview"`
} `yaml:"info"`
Server struct {
Port string `yaml:"port"` //端口
Search bool `yaml:"search"` //允许搜索
Static string `yaml:"static"`
SiteUrl string `yaml:"site_url"` //网站url
Password string `yaml:"password"`
} `yaml:"server"`
AliDrive struct {
ApiUrl string `yaml:"api_url"` //阿里云盘api
MaxFilesCount int `yaml:"max_files_count"`
Drives []Drive `yaml:"drives"`
} `yaml:"ali_drive"`
Database struct {
Type string `yaml:"type"`
User string `yaml:"user"`
Password string `yaml:"password"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Name string `yaml:"name"`
TablePrefix string `yaml:"tablePrefix"`
DBFile string `yaml:"dBFile"`
} `yaml:"database"`
} }
+7 -35
View File
@@ -1,39 +1,11 @@
package conf package conf
import (
"gorm.io/gorm"
"net/http"
)
var (
Debug bool // is debug command
Help bool // is help command
Version bool // is print version command
ConfigFile string // config file
SkipUpdate bool // skip update
Client *http.Client // request client
DB *gorm.DB
Origins []string // allow origins
)
var Conf = new(Config)
const ( const (
VERSION = "v1.0.1" UNKNOWN = iota
FOLDER
ImageThumbnailProcess = "image/resize,w_50" OFFICE
VideoThumbnailProcess = "video/snapshot,t_0,f_jpg,w_50" VIDEO
ImageUrlProcess = "image/resize,w_1920/format,jpeg" AUDIO
ASC = "ASC" TEXT
DESC = "DESC" IMAGE
OrderUpdatedAt = "updated_at"
OrderCreatedAt = "created_at"
OrderSize = "size"
OrderName = "name"
OrderSearch = "type ASC,updated_at DESC"
AccessTokenInvalid = "AccessTokenInvalid"
Bearer = "Bearer\t"
) )
+51
View File
@@ -0,0 +1,51 @@
package conf
import (
"context"
"github.com/eko/gocache/v2/cache"
"github.com/robfig/cron/v3"
"gorm.io/gorm"
)
var (
BuiltAt string
GoVersion string
GitAuthor string
GitCommit string
GitTag string
)
var (
ConfigFile string // config file
Conf *Config
Debug bool
Version bool
Password bool
DB *gorm.DB
Cache *cache.Cache
Ctx = context.TODO()
Cron *cron.Cron
)
var (
TextTypes = []string{"txt", "go", "md"}
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb"}
AudioTypes = []string{"mp3", "flac", "ogg", "m4a"}
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg"}
)
// settings
var (
RawIndexHtml string
IndexHtml string
CheckParent bool
//CustomizeStyle string
//CustomizeScript string
//Favicon string
CheckDown bool
DavUsername string
DavPassword string
)
+151
View File
@@ -0,0 +1,151 @@
package _23pan
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"path/filepath"
"strconv"
"time"
)
var pan123Client = resty.New()
type Pan123TokenResp struct {
Code int `json:"code"`
Data struct {
Token string `json:"token"`
} `json:"data"`
Message string `json:"message"`
}
type Pan123File struct {
FileName string `json:"FileName"`
Size int64 `json:"Size"`
UpdateAt *time.Time `json:"UpdateAt"`
FileId int64 `json:"FileId"`
Type int `json:"Type"`
Etag string `json:"Etag"`
S3KeyFlag string `json:"S3KeyFlag"`
}
type Pan123Files struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
InfoList []Pan123File `json:"InfoList"`
Next string `json:"Next"`
} `json:"data"`
}
type Pan123DownResp struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
DownloadUrl string `json:"DownloadUrl"`
} `json:"data"`
}
func (driver Pan123) Login(account *model.Account) error {
var resp Pan123TokenResp
_, err := pan123Client.R().
SetResult(&resp).
SetBody(drivers.Json{
"passport": account.Username,
"password": account.Password,
}).Post("https://www.123pan.com/api/user/sign_in")
if err != nil {
return err
}
if resp.Code != 200 {
err = fmt.Errorf(resp.Message)
account.Status = resp.Message
} else {
account.Status = "work"
account.AccessToken = resp.Data.Token
}
_ = model.SaveAccount(account)
return err
}
func (driver Pan123) FormatFile(file *Pan123File) *model.File {
f := &model.File{
Id: strconv.FormatInt(file.FileId, 10),
Name: file.FileName,
Size: file.Size,
Driver: driverName,
UpdatedAt: file.UpdateAt,
}
if file.Type == 1 {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(filepath.Ext(file.FileName))
}
return f
}
func (driver Pan123) GetFiles(parentId string, account *model.Account) ([]Pan123File, error) {
next := "0"
res := make([]Pan123File, 0)
for next != "-1" {
var resp Pan123Files
_, err := pan123Client.R().SetResult(&resp).
SetHeader("authorization", "Bearer "+account.AccessToken).
SetQueryParams(map[string]string{
"driveId": "0",
"limit": "100",
"next": next,
"orderBy": account.OrderBy,
"orderDirection": account.OrderDirection,
"parentFileId": parentId,
"trashed": "false",
}).Get("https://www.123pan.com/api/file/list")
if err != nil {
return nil, err
}
log.Debugf("%+v", resp)
if resp.Code != 0 {
if resp.Code == 401 {
err := driver.Login(account)
if err != nil {
return nil, err
}
return driver.GetFiles(parentId, account)
}
return nil, fmt.Errorf(resp.Message)
}
next = resp.Data.Next
res = append(res, resp.Data.InfoList...)
}
return res, nil
}
func (driver Pan123) GetFile(path string, account *model.Account) (*Pan123File, error) {
dir, name := filepath.Split(path)
dir = utils.ParsePath(dir)
_, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
parentFiles, _ := parentFiles_.([]Pan123File)
for _, file := range parentFiles {
if file.FileName == name {
if file.Type != conf.FOLDER {
return &file, err
} else {
return nil, drivers.NotFile
}
}
}
return nil, drivers.PathNotFound
}
func init() {
drivers.RegisterDriver(driverName, &Pan123{})
pan123Client.SetRetryCount(3)
}
+181
View File
@@ -0,0 +1,181 @@
package _23pan
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
)
type Pan123 struct {}
var driverName = "123Pan"
func (driver Pan123) Items() []drivers.Item {
return []drivers.Item{
{
Name: "proxy",
Label: "proxy",
Type: "bool",
Required: true,
Description: "allow proxy",
},
{
Name: "username",
Label: "username",
Type: "string",
Required: true,
Description: "account username/phone number",
},
{
Name: "password",
Label: "password",
Type: "string",
Required: true,
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: "string",
Required: false,
},
{
Name: "order_by",
Label: "order_by",
Type: "select",
Values: "name,fileId,updateAt,createAt",
Required: true,
},
{
Name: "order_direction",
Label: "order_direction",
Type: "select",
Values: "asc,desc",
Required: true,
},
}
}
func (driver Pan123) Save(account *model.Account, old *model.Account) error {
if account.RootFolder == "" {
account.RootFolder = "0"
}
err := driver.Login(account)
return err
}
func (driver Pan123) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driverName,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, drivers.PathNotFound
}
func (driver Pan123) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []Pan123File
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
if err == nil {
rawFiles, _ = cache.([]Pan123File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
return files, nil
}
func (driver Pan123) Link(path string, account *model.Account) (string, error) {
file, err := driver.GetFile(utils.ParsePath(path), account)
if err != nil {
return "", err
}
var resp Pan123DownResp
_, err = pan123Client.R().SetResult(&resp).SetHeader("authorization", "Bearer "+account.AccessToken).
SetBody(drivers.Json{
"driveId": 0,
"etag": file.Etag,
"fileId": file.FileId,
"fileName": file.FileName,
"s3keyFlag": file.S3KeyFlag,
"size": file.Size,
"type": file.Type,
}).Post("https://www.123pan.com/api/file/download_info")
if err != nil {
return "", err
}
if resp.Code != 0 {
if resp.Code == 401 {
err := driver.Login(account)
if err != nil {
return "", err
}
return driver.Link(path, account)
}
return "", fmt.Errorf(resp.Message)
}
return resp.Data.DownloadUrl, nil
}
func (driver Pan123) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("pan123 path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
file.Url, _ = driver.Link(path, account)
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver Pan123) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Del("origin")
}
func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) {
return nil, nil
}
var _ drivers.Driver = (*Pan123)(nil)
+318
View File
@@ -0,0 +1,318 @@
package _89cloud
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
mathRand "math/rand"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
var client189Map map[string]*resty.Client
func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
f := &model.File{
Id: strconv.FormatInt(file.Id, 10),
Name: file.Name,
Size: file.Size,
Driver: "189Cloud",
UpdatedAt: nil,
Thumbnail: file.Icon.SmallUrl,
Url: file.Url,
}
loc, _ := time.LoadLocation("Local")
lastOpTime, err := time.ParseInLocation("2006-01-02 15:04:05", file.LastOpTime, loc)
if err == nil {
f.UpdatedAt = &lastOpTime
}
if file.Size == -1 {
f.Type = conf.FOLDER
f.Size = 0
} else {
f.Type = utils.GetFileType(filepath.Ext(file.Name))
}
return f
}
//func (c Cloud189) GetFile(path string, account *model.Account) (*Cloud189File, error) {
// dir, name := filepath.Split(path)
// dir = utils.ParsePath(dir)
// _, _, err := c.Path(dir, account)
// if err != nil {
// return nil, err
// }
// parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
// parentFiles, _ := parentFiles_.([]Cloud189File)
// for _, file := range parentFiles {
// if file.Name == name {
// if file.Size != -1 {
// return &file, err
// } else {
// return nil, NotFile
// }
// }
// }
// return nil, PathNotFound
//}
type Cloud189Down struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
FileDownloadUrl string `json:"fileDownloadUrl"`
}
type LoginResp struct {
Msg string `json:"msg"`
Result int `json:"result"`
ToUrl string `json:"toUrl"`
}
// Login refer to PanIndex
func (driver Cloud189) Login(account *model.Account) error {
client, ok := client189Map[account.Name]
if !ok {
//cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
client = resty.New()
//client.SetCookieJar(cookieJar)
client.SetRetryCount(3)
}
url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
b := ""
lt := ""
ltText := regexp.MustCompile(`lt = "(.+?)"`)
for i := 0; i < 3; i++ {
res, err := client.R().Get(url)
if err != nil {
return err
}
b = res.String()
ltTextArr := ltText.FindStringSubmatch(b)
if len(ltTextArr) > 0 {
lt = ltTextArr[1]
break
} else {
<-time.After(time.Second)
}
}
if lt == "" {
return fmt.Errorf("get empty login page")
}
captchaToken := regexp.MustCompile(`captchaToken' value='(.+?)'`).FindStringSubmatch(b)[1]
returnUrl := regexp.MustCompile(`returnUrl = '(.+?)'`).FindStringSubmatch(b)[1]
paramId := regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(b)[1]
//reqId := regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(b)[1]
jRsakey := regexp.MustCompile(`j_rsaKey" value="(\S+)"`).FindStringSubmatch(b)[1]
vCodeID := regexp.MustCompile(`picCaptcha\.do\?token\=([A-Za-z0-9\&\=]+)`).FindStringSubmatch(b)[1]
vCodeRS := ""
if vCodeID != "" {
// need ValidateCode
}
userRsa := RsaEncode([]byte(account.Username), jRsakey)
passwordRsa := RsaEncode([]byte(account.Password), jRsakey)
url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
var loginResp LoginResp
res, err := client.R().
SetHeaders(map[string]string{
"lt": lt,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
"Referer": "https://open.e.189.cn/",
"accept": "application/json;charset=UTF-8",
}).SetFormData(map[string]string{
"appKey": "cloud",
"accountType": "01",
"userName": "{RSA}" + userRsa,
"password": "{RSA}" + passwordRsa,
"validateCode": vCodeRS,
"captchaToken": captchaToken,
"returnUrl": returnUrl,
"mailSuffix": "@pan.cn",
"paramId": paramId,
"clientType": "10010",
"dynamicCheck": "FALSE",
"cb_SaveName": "1",
"isOauth2": "false",
}).Post(url)
if err != nil {
return err
}
err = json.Unmarshal(res.Body(), &loginResp)
if err != nil {
log.Error(err.Error())
return err
}
if loginResp.Result != 0 {
return fmt.Errorf(loginResp.Msg)
}
_, err = client.R().Get(loginResp.ToUrl)
if err != nil {
log.Errorf(err.Error())
return err
}
client189Map[account.Name] = client
return nil
}
type Cloud189Error struct {
ErrorCode string `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
type Cloud189File struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
Size int64 `json:"size"`
Icon struct {
SmallUrl string `json:"smallUrl"`
//LargeUrl string `json:"largeUrl"`
} `json:"icon"`
Url string `json:"url"`
}
type Cloud189Folder struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
}
type Cloud189Files struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
FileListAO struct {
Count int `json:"count"`
FileList []Cloud189File `json:"fileList"`
FolderList []Cloud189Folder `json:"folderList"`
} `json:"fileListAO"`
}
func (driver Cloud189) GetFiles(fileId string, account *model.Account) ([]Cloud189File, error) {
client, ok := client189Map[account.Name]
if !ok {
return nil, fmt.Errorf("can't find [%s] client", account.Name)
}
res := make([]Cloud189File, 0)
pageNum := 1
for {
var e Cloud189Error
var resp Cloud189Files
_, err := client.R().SetResult(&resp).SetError(&e).
SetHeader("Accept", "application/json;charset=UTF-8").
SetQueryParams(map[string]string{
"noCache": random(),
"pageSize": "60",
"pageNum": strconv.Itoa(pageNum),
"mediaType": "0",
"folderId": fileId,
"iconOption": "5",
"orderBy": account.OrderBy,
"descending": account.OrderDirection,
}).Get("https://cloud.189.cn/api/open/file/listFiles.action")
if err != nil {
return nil, err
}
if e.ErrorCode != "" {
if e.ErrorCode == "InvalidSessionKey" {
err = driver.Login(account)
if err != nil {
return nil, err
}
return driver.GetFiles(fileId, account)
}
}
if resp.ResCode != 0 {
return nil, fmt.Errorf(resp.ResMessage)
}
if resp.FileListAO.Count == 0 {
break
}
res = append(res, resp.FileListAO.FileList...)
for _, folder := range resp.FileListAO.FolderList {
res = append(res, Cloud189File{
Id: folder.Id,
LastOpTime: folder.LastOpTime,
Name: folder.Name,
Size: -1,
})
}
pageNum++
}
return res, nil
}
func random() string {
return fmt.Sprintf("0.%17v", mathRand.New(mathRand.NewSource(time.Now().UnixNano())).Int63n(100000000000000000))
}
func RsaEncode(origData []byte, j_rsakey string) string {
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
block, _ := pem.Decode(publicKey)
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
pub := pubInterface.(*rsa.PublicKey)
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
if err != nil {
log.Errorf("err: %s", err.Error())
}
return b64tohex(base64.StdEncoding.EncodeToString(b))
}
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"
func int2char(a int) string {
return strings.Split(BI_RM, "")[a]
}
func b64tohex(a string) string {
d := ""
e := 0
c := 0
for i := 0; i < len(a); i++ {
m := strings.Split(a, "")[i]
if m != "=" {
v := strings.Index(b64map, m)
if 0 == e {
e = 1
d += int2char(v >> 2)
c = 3 & v
} else if 1 == e {
e = 2
d += int2char(c<<2 | v>>4)
c = 15 & v
} else if 2 == e {
e = 3
d += int2char(c)
d += int2char(v >> 2)
c = 3 & v
} else {
e = 0
d += int2char(c<<2 | v>>4)
d += int2char(15 & v)
}
}
}
if e == 1 {
d += int2char(c << 2)
}
return d
}
func init() {
drivers.RegisterDriver(driverName, &Cloud189{})
client189Map = make(map[string]*resty.Client, 0)
}
+203
View File
@@ -0,0 +1,203 @@
package _89cloud
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
)
type Cloud189 struct {}
var driverName = "189Cloud"
func (driver Cloud189) Items() []drivers.Item {
return []drivers.Item{
{
Name: "proxy",
Label: "proxy",
Type: "bool",
Required: true,
Description: "allow proxy",
},
{
Name: "username",
Label: "username",
Type: "string",
Required: true,
Description: "account username/phone number",
},
{
Name: "password",
Label: "password",
Type: "string",
Required: true,
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: "string",
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: "select",
Values: "name,size,lastOpTime,createdDate",
Required: true,
},
{
Name: "order_direction",
Label: "desc",
Type: "select",
Values: "true,false",
Required: true,
},
}
}
func (driver Cloud189) Save(account *model.Account, old *model.Account) error {
if old != nil && old.Name != account.Name {
delete(client189Map, old.Name)
}
if err := driver.Login(account); err != nil {
account.Status = err.Error()
_ = model.SaveAccount(account)
return err
}
account.Status = "work"
err := model.SaveAccount(account)
if err != nil {
return err
}
return nil
}
func (driver Cloud189) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driverName,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, drivers.PathNotFound
}
func (driver Cloud189) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []Cloud189File
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
if err == nil {
rawFiles, _ = cache.([]Cloud189File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
return files, nil
}
func (driver Cloud189) Link(path string, account *model.Account) (string, error) {
file, err := driver.File(utils.ParsePath(path), account)
if err != nil {
return "", err
}
if file.Type == conf.FOLDER {
return "", drivers.NotFile
}
client, ok := client189Map[account.Name]
if !ok {
return "", fmt.Errorf("can't find [%s] client", account.Name)
}
var e Cloud189Error
var resp Cloud189Down
_, err = client.R().SetResult(&resp).SetError(&e).
SetHeader("Accept", "application/json;charset=UTF-8").
SetQueryParams(map[string]string{
"noCache": random(),
"fileId": file.Id,
}).Get("https://cloud.189.cn/api/open/file/getFileDownloadUrl.action")
if err != nil {
return "", err
}
if e.ErrorCode != "" {
if e.ErrorCode == "InvalidSessionKey" {
err = driver.Login(account)
if err != nil {
return "", err
}
return driver.Link(path, account)
}
}
if resp.ResCode != 0 {
return "", fmt.Errorf(resp.ResMessage)
}
res, err := drivers.NoRedirectClient.R().Get(resp.FileDownloadUrl)
if err != nil {
return "", err
}
if res.StatusCode() == 302 {
return res.Header().Get("location"), nil
}
return resp.FileDownloadUrl, nil
}
func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("189 path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
file.Url, _ = driver.Link(path, account)
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver Cloud189) Proxy(ctx *gin.Context, account *model.Account) {
ctx.Request.Header.Del("Origin")
}
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
return nil, nil
}
var _ drivers.Driver = (*Cloud189)(nil)
+166
View File
@@ -0,0 +1,166 @@
package alidrive
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"path/filepath"
"time"
)
var aliClient = resty.New()
type AliRespError struct {
Code string `json:"code"`
Message string `json:"message"`
}
type AliFiles struct {
Items []AliFile `json:"items"`
NextMarker string `json:"next_marker"`
}
type AliFile struct {
DriveId string `json:"drive_id"`
CreatedAt *time.Time `json:"created_at"`
FileExtension string `json:"file_extension"`
FileId string `json:"file_id"`
Type string `json:"type"`
Name string `json:"name"`
Category string `json:"category"`
ParentFileId string `json:"parent_file_id"`
UpdatedAt *time.Time `json:"updated_at"`
Size int64 `json:"size"`
Thumbnail string `json:"thumbnail"`
Url string `json:"url"`
}
func (driver AliDrive) FormatFile(file *AliFile) *model.File {
f := &model.File{
Id: file.FileId,
Name: file.Name,
Size: file.Size,
UpdatedAt: file.UpdatedAt,
Thumbnail: file.Thumbnail,
Driver: driverName,
Url: file.Url,
}
if file.Type == "folder" {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(file.FileExtension)
}
if file.Category == "video" {
f.Type = conf.VIDEO
}
if file.Category == "image" {
f.Type = conf.IMAGE
}
return f
}
func (driver AliDrive) GetFiles(fileId string, account *model.Account) ([]AliFile, error) {
marker := "first"
res := make([]AliFile, 0)
for marker != "" {
if marker == "first" {
marker = ""
}
var resp AliFiles
var e AliRespError
_, err := aliClient.R().
SetResult(&resp).
SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(drivers.Json{
"drive_id": account.DriveId,
"fields": "*",
"image_thumbnail_process": "image/resize,w_400/format,jpeg",
"image_url_process": "image/resize,w_1920/format,jpeg",
"limit": account.Limit,
"marker": marker,
"order_by": account.OrderBy,
"order_direction": account.OrderDirection,
"parent_file_id": fileId,
"video_thumbnail_process": "video/snapshot,t_0,f_jpg,ar_auto,w_300",
"url_expire_sec": 14400,
}).Post("https://api.aliyundrive.com/v2/file/list")
if err != nil {
return nil, err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return nil, err
} else {
_ = model.SaveAccount(account)
return driver.GetFiles(fileId, account)
}
}
return nil, fmt.Errorf("%s", e.Message)
}
marker = resp.NextMarker
res = append(res, resp.Items...)
}
return res, nil
}
func (driver AliDrive) GetFile(path string, account *model.Account) (*AliFile, error) {
dir, name := filepath.Split(path)
dir = utils.ParsePath(dir)
_, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
parentFiles, _ := parentFiles_.([]AliFile)
for _, file := range parentFiles {
if file.Name == name {
if file.Type == "file" {
return &file, err
} else {
return nil, fmt.Errorf("not file")
}
}
}
return nil, drivers.PathNotFound
}
func (driver AliDrive) RefreshToken(account *model.Account) error {
url := "https://auth.aliyundrive.com/v2/account/token"
var resp drivers.TokenResp
var e AliRespError
_, err := aliClient.R().
//ForceContentType("application/json").
SetBody(drivers.Json{"refresh_token": account.RefreshToken, "grant_type": "refresh_token"}).
SetResult(&resp).
SetError(&e).
Post(url)
if err != nil {
account.Status = err.Error()
return err
}
log.Debugf("%+v,%+v", resp, e)
if e.Code != "" {
account.Status = e.Message
return fmt.Errorf("failed to refresh token: %s", e.Message)
} else {
account.Status = "work"
}
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
return nil
}
func init() {
drivers.RegisterDriver(driverName, &AliDrive{})
aliClient.
SetRetryCount(3).
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36").
SetHeader("content-type", "application/json").
SetHeader("origin", "https://aliyundrive.com")
}
+253
View File
@@ -0,0 +1,253 @@
package alidrive
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
"path/filepath"
)
type AliDrive struct{}
var driverName = "AliDrive"
func (driver AliDrive) Items() []drivers.Item {
return []drivers.Item{
{
Name: "proxy",
Label: "proxy",
Type: "bool",
Required: true,
Description: "allow proxy",
},
{
Name: "order_by",
Label: "order_by",
Type: "select",
Values: "name,size,updated_at,created_at",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: "select",
Values: "ASC,DESC",
Required: false,
},
{
Name: "refresh_token",
Label: "refresh token",
Type: "string",
Required: true,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: "string",
Required: false,
},
{
Name: "limit",
Label: "limit",
Type: "number",
Required: false,
Description: ">0 and <=200",
},
}
}
func (driver AliDrive) Save(account *model.Account, old *model.Account) error {
if old != nil {
conf.Cron.Remove(cron.EntryID(old.CronId))
}
if account.RootFolder == "" {
account.RootFolder = "root"
}
if account.Limit == 0 {
account.Limit = 200
}
err := driver.RefreshToken(account)
if err != nil {
return err
}
var resp drivers.Json
_, _ = aliClient.R().SetResult(&resp).
SetBody("{}").
SetHeader("authorization", "Bearer\t"+account.AccessToken).
Post("https://api.aliyundrive.com/v2/user/get")
log.Debugf("user info: %+v", resp)
account.DriveId = resp["default_drive_id"].(string)
cronId, err := conf.Cron.AddFunc("@every 2h", func() {
name := account.Name
log.Debugf("ali account name: %s", name)
newAccount, ok := model.GetAccount(name)
log.Debugf("ali account: %+v", newAccount)
if !ok {
return
}
err = driver.RefreshToken(&newAccount)
_ = model.SaveAccount(&newAccount)
})
if err != nil {
return err
}
account.CronId = int(cronId)
err = model.SaveAccount(account)
if err != nil {
return err
}
return nil
}
func (driver AliDrive) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driverName,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, drivers.PathNotFound
}
func (driver AliDrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []AliFile
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
if err == nil {
rawFiles, _ = cache.([]AliFile)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
return files, nil
}
func (driver AliDrive) Link(path string, account *model.Account) (string, error) {
file, err := driver.File(path, account)
if err != nil {
return "", err
}
var resp drivers.Json
var e AliRespError
_, err = aliClient.R().SetResult(&resp).
SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(drivers.Json{
"drive_id": account.DriveId,
"file_id": file.Id,
"expire_sec": 14400,
}).Post("https://api.aliyundrive.com/v2/file/get_download_url")
if err != nil {
return "", err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return "", err
} else {
_ = model.SaveAccount(account)
return driver.Link(path, account)
}
}
return "", fmt.Errorf("%s", e.Message)
}
return resp["url"].(string), nil
}
func (driver AliDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("ali path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
file.Url, _ = driver.Link(path, account)
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver AliDrive) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Del("Origin")
c.Request.Header.Set("Referer", "https://www.aliyundrive.com/")
}
func (driver AliDrive) Preview(path string, account *model.Account) (interface{}, error) {
file, err := driver.GetFile(path, account)
if err != nil {
return nil, err
}
// office
var resp drivers.Json
var e AliRespError
var url string
req := drivers.Json{
"drive_id": account.DriveId,
"file_id": file.FileId,
}
switch file.Category {
case "doc":
{
url = "https://api.aliyundrive.com/v2/file/get_office_preview_url"
req["access_token"] = account.AccessToken
}
case "video":
{
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
req["category"] = "live_transcoding"
}
default:
return nil, fmt.Errorf("don't support")
}
_, err = aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(req).Post(url)
if err != nil {
return nil, err
}
if e.Code != "" {
return nil, fmt.Errorf("%s", e.Message)
}
return resp, nil
}
var _ drivers.Driver = (*AliDrive)(nil)
+72
View File
@@ -0,0 +1,72 @@
package drivers
import (
"github.com/Xhofe/alist/model"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http"
)
type Driver interface {
Items() []Item
Save(account *model.Account, old *model.Account) error
File(path string, account *model.Account) (*model.File, error)
Files(path string, account *model.Account) ([]model.File, error)
Link(path string, account *model.Account) (string, error)
Path(path string, account *model.Account) (*model.File, []model.File, error)
Proxy(c *gin.Context, account *model.Account)
Preview(path string, account *model.Account) (interface{}, error)
// TODO
//Search(path string, keyword string, account *model.Account) ([]*model.File, error)
//MakeDir(path string, account *model.Account) error
//Move(src string, des string, account *model.Account) error
//Delete(path string) error
//Upload(file *fs.File, path string, account *model.Account) error
}
type Item struct {
Name string `json:"name"`
Label string `json:"label"`
Type string `json:"type"`
Values string `json:"values"`
Required bool `json:"required"`
Description string `json:"description"`
}
type TokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
var driversMap = map[string]Driver{}
func RegisterDriver(name string, driver Driver) {
log.Infof("register driver: [%s]", name)
driversMap[name] = driver
}
func GetDriver(name string) (driver Driver, ok bool) {
driver, ok = driversMap[name]
return
}
func GetDrivers() map[string][]Item {
res := make(map[string][]Item, 0)
for k, v := range driversMap {
res[k] = v.Items()
}
return res
}
type Json map[string]interface{}
var NoRedirectClient *resty.Client
func init() {
NoRedirectClient = resty.New().SetRedirectPolicy(
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}),
)
}
+8
View File
@@ -0,0 +1,8 @@
package drivers
import "fmt"
var (
PathNotFound = fmt.Errorf("path not found")
NotFile = fmt.Errorf("not file")
)
+162
View File
@@ -0,0 +1,162 @@
package googledrive
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
)
type GoogleDrive struct{}
var driverName = "GoogleDrive"
func (driver GoogleDrive) Items() []drivers.Item {
return []drivers.Item{
{
Name: "client_id",
Label: "client id",
Type: "string",
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Type: "string",
Required: true,
},
{
Name: "refresh_token",
Label: "refresh token",
Type: "string",
Required: true,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: "string",
Required: true,
},
}
}
func (driver GoogleDrive) Save(account *model.Account, old *model.Account) error {
account.Proxy = true
err := driver.RefreshToken(account)
if err != nil {
account.Status = err.Error()
_ = model.SaveAccount(account)
return err
}
account.Status = "work"
_ = model.SaveAccount(account)
return nil
}
func (driver GoogleDrive) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driverName,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, drivers.PathNotFound
}
func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []GoogleFile
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
if err == nil {
rawFiles, _ = cache.([]GoogleFile)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
return files, nil
}
func (driver GoogleDrive) Link(path string, account *model.Account) (string, error) {
file, err := driver.File(path, account)
if err != nil {
return "", err
}
if file.Type == conf.FOLDER {
return "", drivers.NotFile
}
link := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?includeItemsFromAllDrives=true&supportsAllDrives=true", file.Id)
var e GoogleError
_, _ = googleClient.R().SetError(&e).
SetHeader("Authorization", "Bearer "+account.AccessToken).
Get(link)
if e.Error.Code != 0 {
if e.Error.Code == 401 {
err = driver.RefreshToken(account)
if err != nil {
_ = model.SaveAccount(account)
return "", err
}
return driver.Link(path, account)
}
return "", fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
}
return link + "&alt=media", nil
}
func (driver GoogleDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("google path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
//file.Url, _ = driver.Link(path, account)
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver GoogleDrive) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Add("Authorization", "Bearer "+account.AccessToken)
}
func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, nil
}
+160
View File
@@ -0,0 +1,160 @@
package googledrive
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
"path/filepath"
"strconv"
"time"
)
var googleClient = resty.New()
type GoogleTokenError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
func (driver GoogleDrive) RefreshToken(account *model.Account) error {
url := "https://www.googleapis.com/oauth2/v4/token"
var resp drivers.TokenResp
var e GoogleTokenError
_, err := googleClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
"refresh_token": account.RefreshToken,
"grant_type": "refresh_token",
}).Post(url)
if err != nil {
return err
}
if e.Error != "" {
return fmt.Errorf(e.Error)
}
account.AccessToken = resp.AccessToken
account.Status = "work"
return nil
}
type GoogleFile struct {
Id string `json:"id"`
Name string `json:"name"`
MimeType string `json:"mimeType"`
ModifiedTime *time.Time `json:"modifiedTime"`
Size string `json:"size"`
}
func (driver GoogleDrive) IsDir(mimeType string) bool {
return mimeType == "application/vnd.google-apps.folder" || mimeType == "application/vnd.google-apps.shortcut"
}
func (driver GoogleDrive) FormatFile(file *GoogleFile) *model.File {
f := &model.File{
Id: file.Id,
Name: file.Name,
Driver: driverName,
UpdatedAt: file.ModifiedTime,
Thumbnail: "",
Url: "",
}
if driver.IsDir(file.MimeType) {
f.Type = conf.FOLDER
} else {
size, _ := strconv.ParseInt(file.Size, 10, 64)
f.Size = size
f.Type = utils.GetFileType(filepath.Ext(file.Name))
}
return f
}
type GoogleFiles struct {
NextPageToken string `json:"nextPageToken"`
Files []GoogleFile `json:"files"`
}
type GoogleError struct {
Error struct {
Errors []struct {
Domain string `json:"domain"`
Reason string `json:"reason"`
Message string `json:"message"`
LocationType string `json:"location_type"`
Location string `json:"location"`
}
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func (driver GoogleDrive) GetFiles(id string, account *model.Account) ([]GoogleFile, error) {
pageToken := "first"
res := make([]GoogleFile, 0)
for pageToken != "" {
if pageToken == "first" {
pageToken = ""
}
var resp GoogleFiles
var e GoogleError
_, err := googleClient.R().SetResult(&resp).SetError(&e).
SetHeader("Authorization", "Bearer "+account.AccessToken).
SetQueryParams(map[string]string{
"orderBy": "folder,name,modifiedTime desc",
"fields": "files(id,name,mimeType,size,modifiedTime),nextPageToken",
"pageSize": "1000",
"q": fmt.Sprintf("'%s' in parents and trashed = false", id),
"includeItemsFromAllDrives": "true",
"supportsAllDrives": "true",
"pageToken": pageToken,
}).Get("https://www.googleapis.com/drive/v3/files")
if err != nil {
return nil, err
}
if e.Error.Code != 0 {
if e.Error.Code == 401 {
err = driver.RefreshToken(account)
if err != nil {
_ = model.SaveAccount(account)
return nil, err
}
return driver.GetFiles(id, account)
}
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
}
pageToken = resp.NextPageToken
res = append(res, resp.Files...)
}
return res, nil
}
//func (driver GoogleDrive) GetFile(path string, account *model.Account) (*GoogleFile, error) {
// dir, name := filepath.Split(path)
// dir = utils.ParsePath(dir)
// _, _, err := driver.Path(dir, account)
// if err != nil {
// return nil, err
// }
// parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
// parentFiles, _ := parentFiles_.([]GoogleFile)
// for _, file := range parentFiles {
// if file.Name == name {
// if !driver.IsDir(file.MimeType) {
// return &file, err
// } else {
// return nil, drivers.NotFile
// }
// }
// }
// return nil, drivers.PathNotFound
//}
var _ drivers.Driver = (*GoogleDrive)(nil)
func init() {
drivers.RegisterDriver(driverName, &GoogleDrive{})
googleClient.SetRetryCount(3)
}
+157
View File
@@ -0,0 +1,157 @@
package native
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
type Native struct{}
var driverName = "Native"
func (driver Native) Items() []drivers.Item {
return []drivers.Item{
{
Name: "root_folder",
Label: "root folder path",
Type: "string",
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: "select",
Values: "name,size,updated_at",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: "select",
Values: "ASC,DESC",
Required: false,
},
}
}
func (driver Native) Save(account *model.Account, old *model.Account) error {
log.Debugf("save a account: [%s]", account.Name)
if !utils.Exists(account.RootFolder) {
account.Status = fmt.Sprintf("[%s] not exist", account.RootFolder)
_ = model.SaveAccount(account)
return fmt.Errorf("[%s] not exist", account.RootFolder)
}
account.Status = "work"
account.Proxy = true
err := model.SaveAccount(account)
if err != nil {
return err
}
return nil
}
func (driver Native) File(path string, account *model.Account) (*model.File, error) {
fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) {
return nil, drivers.PathNotFound
}
f, err := os.Stat(fullPath)
if err != nil {
return nil, err
}
time := f.ModTime()
file := &model.File{
Name: f.Name(),
Size: f.Size(),
UpdatedAt: &time,
Driver: driverName,
}
if f.IsDir() {
file.Type = conf.FOLDER
} else {
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
}
return file, nil
}
func (driver Native) Files(path string, account *model.Account) ([]model.File, error) {
fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) {
return nil, drivers.PathNotFound
}
files := make([]model.File, 0)
rawFiles, err := ioutil.ReadDir(fullPath)
if err != nil {
return nil, err
}
for _, f := range rawFiles {
if strings.HasPrefix(f.Name(), ".") {
continue
}
time := f.ModTime()
file := model.File{
Name: f.Name(),
Size: f.Size(),
Type: 0,
UpdatedAt: &time,
Driver: driverName,
}
if f.IsDir() {
file.Type = conf.FOLDER
} else {
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
}
files = append(files, file)
}
model.SortFiles(files, account)
return files, nil
}
func (driver Native) Link(path string, account *model.Account) (string, error) {
fullPath := filepath.Join(account.RootFolder, path)
s, err := os.Stat(fullPath)
if err != nil {
return "", err
}
if s.IsDir() {
return "", fmt.Errorf("can't down folder")
}
return fullPath, nil
}
func (driver Native) Path(path string, account *model.Account) (*model.File, []model.File, error) {
log.Debugf("native path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
//file.Url, _ = driver.Link(path, account)
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
model.SortFiles(files, account)
return nil, files, nil
}
func (driver Native) Proxy(c *gin.Context, account *model.Account) {
// unnecessary
}
func (driver Native) Preview(path string, account *model.Account) (interface{}, error) {
return nil, fmt.Errorf("no need")
}
var _ drivers.Driver = (*Native)(nil)
+9
View File
@@ -0,0 +1,9 @@
package native
import (
"github.com/Xhofe/alist/drivers"
)
func init() {
drivers.RegisterDriver(driverName, &Native{})
}
+209
View File
@@ -0,0 +1,209 @@
package onedrive
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
"path/filepath"
)
type Onedrive struct{}
var driverName = "Onedrive"
func (driver Onedrive) Items() []drivers.Item {
return []drivers.Item{
{
Name: "proxy",
Label: "proxy",
Type: "bool",
Required: true,
Description: "allow proxy",
},
{
Name: "zone",
Label: "zone",
Type: "select",
Required: true,
Values: "global,cn,us,de",
Description: "",
},
{
Name: "onedrive_type",
Label: "onedrive type",
Type: "select",
Required: true,
Values: "onedrive,sharepoint",
},
{
Name: "client_id",
Label: "client id",
Type: "string",
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Type: "string",
Required: true,
},
{
Name: "redirect_uri",
Label: "redirect uri",
Type: "string",
Required: true,
},
{
Name: "refresh_token",
Label: "refresh token",
Type: "string",
Required: true,
},
{
Name: "site_id",
Label: "site id",
Type: "string",
Required: false,
},
{
Name: "root_folder",
Label: "root folder path",
Type: "string",
Required: false,
},
{
Name: "order_by",
Label: "order_by",
Type: "select",
Values: "name,size,lastModifiedDateTime",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: "select",
Values: "asc,desc",
Required: false,
},
}
}
func (driver Onedrive) Save(account *model.Account, old *model.Account) error {
_, ok := onedriveHostMap[account.Zone]
if !ok {
return fmt.Errorf("no [%s] zone", account.Zone)
}
if old != nil {
conf.Cron.Remove(cron.EntryID(old.CronId))
}
account.RootFolder = utils.ParsePath(account.RootFolder)
err := driver.RefreshToken(account)
if err != nil {
return err
}
cronId, err := conf.Cron.AddFunc("@every 1h", func() {
name := account.Name
log.Debugf("onedrive account name: %s", name)
newAccount, ok := model.GetAccount(name)
log.Debugf("onedrive account: %+v", newAccount)
if !ok {
return
}
err = driver.RefreshToken(&newAccount)
_ = model.SaveAccount(&newAccount)
})
if err != nil {
return err
}
account.CronId = int(cronId)
err = model.SaveAccount(account)
if err != nil {
return err
}
return nil
}
func (driver Onedrive) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driverName,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, drivers.PathNotFound
}
func (driver Onedrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
rawFiles, err := driver.GetFiles(account, path)
if err != nil {
return nil, err
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), files, nil)
return files, nil
}
func (driver Onedrive) Link(path string, account *model.Account) (string, error) {
file, err := driver.GetFile(account, path)
if err != nil {
return "", err
}
if file.File.MimeType == "" {
return "", fmt.Errorf("can't down folder")
}
return file.Url, nil
}
func (driver Onedrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
log.Debugf("onedrive path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
//file.Url, _ = driver.Link(path, account)
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver Onedrive) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Del("Origin")
}
func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, nil
}
+185
View File
@@ -0,0 +1,185 @@
package onedrive
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"path/filepath"
"time"
)
var oneClient = resty.New()
type Host struct {
Oauth string
Api string
}
var onedriveHostMap = map[string]Host{
"global": {
Oauth: "https://login.microsoftonline.com",
Api: "https://graph.microsoft.com",
},
"cn": {
Oauth: "https://login.chinacloudapi.cn",
Api: "https://microsoftgraph.chinacloudapi.cn",
},
"us": {
Oauth: "https://login.microsoftonline.us",
Api: "https://graph.microsoft.us",
},
"de": {
Oauth: "https://login.microsoftonline.de",
Api: "https://graph.microsoft.de",
},
}
func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string {
path = filepath.Join(account.RootFolder, path)
log.Debugf(path)
host, _ := onedriveHostMap[account.Zone]
if auth {
return host.Oauth
}
switch account.OnedriveType {
case "onedrive":
{
if path == "/" || path == "\\" {
return fmt.Sprintf("%s/v1.0/me/drive/root", host.Api)
} else {
return fmt.Sprintf("%s/v1.0/me/drive/root:%s:", host.Api, path)
}
}
case "sharepoint":
{
if path == "/" {
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root", host.Api, account.SiteId)
} else {
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root:%s:", host.Api, account.SiteId, path)
}
}
default:
return ""
}
}
type OneTokenErr struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
func (driver Onedrive) RefreshToken(account *model.Account) error {
url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
var resp drivers.TokenResp
var e OneTokenErr
_, err := oneClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token",
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
"redirect_uri": account.RedirectUri,
"refresh_token": account.RefreshToken,
}).Post(url)
if err != nil {
account.Status = err.Error()
return err
}
if e.Error != "" {
account.Status = e.ErrorDescription
return fmt.Errorf("%s", e.ErrorDescription)
} else {
account.Status = "work"
}
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
return nil
}
type OneFile struct {
Name string `json:"name"`
Size int64 `json:"size"`
LastModifiedDateTime *time.Time `json:"lastModifiedDateTime"`
Url string `json:"@microsoft.graph.downloadUrl"`
File struct {
MimeType string `json:"mimeType"`
} `json:"file"`
}
type OneFiles struct {
Value []OneFile `json:"value"`
NextLink string `json:"@odata.nextLink"`
}
type OneRespErr struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func (driver Onedrive) FormatFile(file *OneFile) *model.File {
f := &model.File{
Name: file.Name,
Size: file.Size,
UpdatedAt: file.LastModifiedDateTime,
Driver: driverName,
Url: file.Url,
}
if file.File.MimeType == "" {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(filepath.Ext(file.Name))
}
return f
}
func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile, error) {
var res []OneFile
nextLink := driver.GetMetaUrl(account, false, path) + "/children"
if account.OrderBy != "" {
nextLink += fmt.Sprintf("?orderby=%s", account.OrderBy)
if account.OrderDirection != "" {
nextLink += fmt.Sprintf(" %s", account.OrderDirection)
}
}
for nextLink != "" {
var files OneFiles
var e OneRespErr
_, err := oneClient.R().SetResult(&files).SetError(&e).
SetHeader("Authorization", "Bearer "+account.AccessToken).
Get(nextLink)
if err != nil {
return nil, err
}
if e.Error.Code != "" {
return nil, fmt.Errorf("%s", e.Error.Message)
}
res = append(res, files.Value...)
nextLink = files.NextLink
}
return res, nil
}
func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, error) {
var file OneFile
var e OneRespErr
_, err := oneClient.R().SetResult(&file).SetError(&e).
SetHeader("Authorization", "Bearer "+account.AccessToken).
Get(driver.GetMetaUrl(account, false, path))
if err != nil {
return nil, err
}
if e.Error.Code != "" {
return nil, fmt.Errorf("%s", e.Error.Message)
}
return &file, nil
}
var _ drivers.Driver = (*Onedrive)(nil)
func init() {
drivers.RegisterDriver(driverName, &Onedrive{})
oneClient.SetRetryCount(3)
}
+63 -19
View File
@@ -1,25 +1,69 @@
module github.com/Xhofe/alist module github.com/Xhofe/alist
go 1.15 go 1.17
require ( require (
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e github.com/eko/gocache/v2 v2.1.0
github.com/gin-gonic/gin v1.6.3 github.com/gin-gonic/gin v1.7.4
github.com/go-playground/validator/v10 v10.4.1 // indirect github.com/go-playground/validator/v10 v10.9.0
github.com/golang/protobuf v1.4.3 // indirect github.com/go-resty/resty/v2 v2.6.0
github.com/json-iterator/go v1.1.10 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-sqlite3 v1.14.6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/robfig/cron/v3 v3.0.0 github.com/robfig/cron/v3 v3.0.0
github.com/sirupsen/logrus v1.7.0 github.com/sirupsen/logrus v1.8.1
github.com/ugorji/go v1.2.2 // indirect gorm.io/driver/mysql v1.1.2
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 // indirect gorm.io/driver/postgres v1.1.2
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 // indirect gorm.io/driver/sqlite v1.1.6
google.golang.org/protobuf v1.25.0 // indirect gorm.io/gorm v1.21.16
gopkg.in/yaml.v2 v2.4.0 )
gorm.io/driver/mysql v1.0.5
gorm.io/driver/sqlite v1.1.4 require (
gorm.io/gorm v1.21.3 github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gin-contrib/cors v1.3.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-redis/redis/v8 v8.9.0 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.10.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.1.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.8.1 // indirect
github.com/jackc/pgx/v4 v4.13.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pegasus-kv/thrift v0.13.0 // indirect
github.com/prometheus/client_golang v1.10.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.18.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/ugorji/go/codec v1.2.6 // indirect
go.opentelemetry.io/otel v0.20.0 // indirect
go.opentelemetry.io/otel/metric v0.20.0 // indirect
go.opentelemetry.io/otel/trace v0.20.0 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3 // indirect
) )
+631 -66
View File
@@ -1,181 +1,746 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 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=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10=
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/allegro/bigcache/v2 v2.2.5 h1:mRc8r6GQjuJsmSKQNPsR5jQVXc8IJ1xsW5YXUYMLfqI=
github.com/allegro/bigcache/v2 v2.2.5/go.mod h1:FppZsIO+IZk7gCuj5FiIDHGygD9xvWQcqg1uIPMb6tY=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coocood/freecache v1.1.1 h1:uukNF7QKCZEdZ9gAV7WQzvh0SbjwdMF6m3x3rxEkaPc=
github.com/coocood/freecache v1.1.1/go.mod h1:OKrEjkGVoxZhyWAJoeFi5BMLUJm2Tit0kpGkIr7NGYY=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/dgraph-io/ristretto v0.0.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDzSIBI=
github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/eko/gocache/v2 v2.1.0 h1:ljFKAAa5hHsrsSaBvyx0g9a/A9lZSUrf4jBjErQd7gc=
github.com/eko/gocache/v2 v2.1.0/go.mod h1:u+EpYjCVsOpeqvDLzinOVLjLxwHJjO+NT4LS2+8XnCU=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e h1:8bZpGwoPxkaivQPrAbWl+7zjjUcbFUnYp7yQcx2r2N0=
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e/go.mod h1:VhW/Ch/3FhimwZb8Oj+qJmdMmoB8r7lmJ5auRjm50oQ=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-redis/redis/v8 v8.9.0 h1:FTTbB7WqlXfVNdVv0SsxA+oVi0bAwit6bMe3IUucq2o=
github.com/go-redis/redis/v8 v8.9.0/go.mod h1:ik7vb7+gm8Izylxu6kf6wG26/t2VljgCfSQ1DM4O1uU=
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 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.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.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-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.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 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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.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.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.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.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.4/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.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
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/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU=
github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570=
github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4=
github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.2 h1:60ZHIOcsJlo3bJm9CbTVu7OSqT2mxaEmyQbK2NwCkn0= github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
github.com/ugorji/go v1.2.2/go.mod h1:bitgyERdV7L7Db/Z5gfd5v2NQMNhhiFiZwpgMw2SP7k= github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.2 h1:08Gah8d+dXj4cZNUHhtuD/S4PXD5WpVbj5B8/ClELAQ= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
github.com/ugorji/go/codec v1.2.2/go.mod h1:OM8g7OAy52uYl3Yk+RE/3AS1nXFn1Wh4PPLtupCxbuU= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8=
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
go.opentelemetry.io/otel/oteltest v0.20.0 h1:HiITxCawalo5vQzdHfKeZurV8x7ljcqAgiWzF6Vaeaw=
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/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-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 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-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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190125091013-d26f9f9a57f3/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-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-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-20190404232315-eb5bcb51f2a3/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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-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-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-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 h1:+CBz4km/0KPU3RGTwARGh/noP3bEwtHcq+0YcBQM2JQ= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 h1:SeSEfdIxyvwGJliREIJhRPPXvW6sDlLT+UQ3B0hD0NA=
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/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.0/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.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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/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-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-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-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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/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-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 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-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 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-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 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.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.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 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs=
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.0.5 h1:WAAmvLK2rG0tCOqrf5XcLi2QUwugd4rcVJ/W3aoon9o= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohLI= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM= gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw= gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM=
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/driver/postgres v1.1.2 h1:Amy3hCvLqM+/ICzjCnQr8wKFLVJTeOTdlMT7kCP+J1Q=
gorm.io/gorm v1.21.1 h1:ACwUZ+jzH8eG8zxgqTnMIdgWd+lGfCKZTUxL/uQ1ZQo= gorm.io/driver/postgres v1.1.2/go.mod h1:/AGV0zvqF3mt9ZtzLzQmXWQ/5vr+1V1TyHZGZVjzmwI=
gorm.io/gorm v1.21.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/driver/sqlite v1.1.6 h1:p3U8WXkVFTOLPED4JjrZExfndjOtya3db8w9/vEMNyI=
gorm.io/gorm v1.21.3 h1:qDFi55ZOsjZTwk5eN+uhAmHi8GysJ/qCTichM/yO7ME= gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8=
gorm.io/gorm v1.21.3/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/gorm v1.21.16 h1:YBIQLtP5PLfZQz59qfrq7xbrK7KWQ+JsXXCH/THlMqs=
gorm.io/gorm v1.21.16/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/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.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3 h1:FErmbNIJruD5GT2oVEjtPn5Ar5+rcWJsC8/PPUkR0s4=
k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
+125
View File
@@ -0,0 +1,125 @@
package model
import (
"github.com/Xhofe/alist/conf"
"github.com/robfig/cron/v3"
"time"
)
type Account struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"unique" binding:"required"`
Index int `json:"index"`
Type string `json:"type"`
Username string `json:"username"`
Password string `json:"password"`
RefreshToken string `json:"refresh_token"`
AccessToken string `json:"access_token"`
RootFolder string `json:"root_folder"`
Status string `json:"status"`
CronId int
DriveId string
Limit int `json:"limit"`
OrderBy string `json:"order_by"`
OrderDirection string `json:"order_direction"`
Proxy bool `json:"proxy"`
UpdatedAt *time.Time `json:"updated_at"`
Search bool `json:"search"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Zone string `json:"zone"`
RedirectUri string `json:"redirect_uri"`
SiteUrl string `json:"site_url"`
SiteId string `json:"site_id"`
OnedriveType string `json:"onedrive_type"`
}
var accountsMap = map[string]Account{}
// SaveAccount save account to database
func SaveAccount(account *Account) error {
if err := conf.DB.Save(account).Error; err != nil {
return err
}
RegisterAccount(*account)
return nil
}
func CreateAccount(account *Account) error {
if err := conf.DB.Create(account).Error; err != nil {
return err
}
RegisterAccount(*account)
return nil
}
func DeleteAccount(id uint) error {
var account Account
account.ID = id
if err := conf.DB.First(&account).Error; err != nil {
return err
}
name := account.Name
conf.Cron.Remove(cron.EntryID(account.CronId))
if err := conf.DB.Delete(&account).Error; err != nil {
return err
}
delete(accountsMap, name)
return nil
}
func DeleteAccountFromMap(name string) {
delete(accountsMap, name)
}
func AccountsCount() int {
return len(accountsMap)
}
func RegisterAccount(account Account) {
accountsMap[account.Name] = account
}
func GetAccount(name string) (Account, bool) {
if len(accountsMap) == 1 {
for _, v := range accountsMap {
return v, true
}
}
account, ok := accountsMap[name]
return account, ok
}
func GetAccountById(id uint) (*Account, error) {
var account Account
account.ID = id
if err := conf.DB.First(&account).Error; err != nil {
return nil, err
}
return &account, nil
}
func GetAccountFiles() ([]File, error) {
files := make([]File, 0)
var accounts []Account
if err := conf.DB.Order("`index`").Find(&accounts).Error; err != nil {
return nil, err
}
for _, v := range accounts {
files = append(files, File{
Name: v.Name,
Size: 0,
Type: conf.FOLDER,
UpdatedAt: v.UpdatedAt,
})
}
return files, nil
}
func GetAccounts() ([]Account, error) {
var accounts []Account
if err := conf.DB.Order("`index`").Find(&accounts).Error; err != nil {
return nil, err
}
return accounts, nil
}
+66
View File
@@ -0,0 +1,66 @@
package model
import (
"github.com/Xhofe/alist/conf"
"sort"
"strings"
"time"
)
type File struct {
Id string `json:"-"`
Name string `json:"name"`
Size int64 `json:"size"`
Type int `json:"type"`
Driver string `json:"driver"`
UpdatedAt *time.Time `json:"updated_at"`
Thumbnail string `json:"thumbnail"`
Url string `json:"url"`
}
func SortFiles(files []File, account *Account) {
if account.OrderBy == "" {
return
}
sort.Slice(files, func(i, j int) bool {
switch account.OrderBy {
case "name":
{
c := strings.Compare(files[i].Name, files[j].Name)
if account.OrderDirection == "DESC" {
return c >= 0
}
return c <= 0
}
case "size":
{
if account.OrderDirection == "DESC" {
return files[i].Size >= files[j].Size
}
return files[i].Size <= files[j].Size
}
case "updated_at":
if account.OrderDirection == "DESC" {
return files[i].UpdatedAt.After(*files[j].UpdatedAt)
}
return files[i].UpdatedAt.Before(*files[j].UpdatedAt)
}
return false
})
}
func (f File) GetSize() uint64 {
return uint64(f.Size)
}
func (f File) GetName() string {
return f.Name
}
func (f File) ModTime() time.Time {
return *f.UpdatedAt
}
func (f File) IsDir() bool {
return f.Type == conf.FOLDER
}
+44
View File
@@ -0,0 +1,44 @@
package model
import (
"github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
)
type Meta struct {
ID uint `json:"id" gorm:"primaryKey"`
Path string `json:"path" gorm:"unique" binding:"required"`
Password string `json:"password"`
Hide string `json:"hide"`
}
func GetMetaByPath(path string) (*Meta, error) {
var meta Meta
err := conf.DB.Where("path = ?", path).First(&meta).Error
if err != nil {
return nil, err
}
return &meta, nil
}
func SaveMeta(meta Meta) error {
return conf.DB.Save(&meta).Error
}
func CreateMeta(meta Meta) error {
return conf.DB.Create(&meta).Error
}
func DeleteMeta(id uint) error {
meta := Meta{ID: id}
log.Debugf("delete meta: %+v", meta)
return conf.DB.Delete(&meta).Error
}
func GetMetas() (*[]Meta, error) {
var metas []Meta
if err := conf.DB.Find(&metas).Error; err != nil {
return nil, err
}
return &metas, nil
}
+97
View File
@@ -0,0 +1,97 @@
package model
import (
"github.com/Xhofe/alist/conf"
"strings"
)
const (
PUBLIC = iota
PRIVATE
CONST
)
type SettingItem struct {
Key string `json:"key" gorm:"primaryKey" binding:"required"`
Value string `json:"value"`
Description string `json:"description"`
Type string `json:"type"`
Group int `json:"group"`
Values string `json:"values"`
}
func SaveSettings(items []SettingItem) error {
return conf.DB.Save(items).Error
}
func SaveSetting(item SettingItem) error {
return conf.DB.Save(item).Error
}
func GetSettingsPublic() (*[]SettingItem, error) {
var items []SettingItem
if err := conf.DB.Where("`group` <> ?", 1).Find(&items).Error; err != nil {
return nil, err
}
return &items, nil
}
func GetSettings() (*[]SettingItem, error) {
var items []SettingItem
if err := conf.DB.Find(&items).Error; err != nil {
return nil, err
}
return &items, nil
}
func GetSettingByKey(key string) (*SettingItem, error) {
var items SettingItem
if err := conf.DB.Where("`key` = ?", key).First(&items).Error; err != nil {
return nil, err
}
return &items, nil
}
func LoadSettings() {
textTypes, err := GetSettingByKey("text types")
if err == nil {
conf.TextTypes = strings.Split(textTypes.Value, ",")
}
checkParent, err := GetSettingByKey("check parent folder")
if err == nil {
conf.CheckParent = checkParent.Value == "true"
}
checkDown,err := GetSettingByKey("check down link")
if err == nil {
conf.CheckDown = checkDown.Value == "true"
}
favicon, err := GetSettingByKey("favicon")
if err == nil {
//conf.Favicon = favicon.Value
conf.IndexHtml = strings.Replace(conf.RawIndexHtml, "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png", favicon.Value, 1)
}
title, err := GetSettingByKey("title")
if err == nil {
//conf.CustomizeStyle = customizeStyle.Value
conf.IndexHtml = strings.Replace(conf.IndexHtml, "Loading...", title.Value, 1)
}
customizeStyle, err := GetSettingByKey("customize style")
if err == nil {
//conf.CustomizeStyle = customizeStyle.Value
conf.IndexHtml = strings.Replace(conf.IndexHtml, "/* customize-style */", customizeStyle.Value, 1)
}
customizeScript, err := GetSettingByKey("customize script")
if err == nil {
//conf.CustomizeStyle = customizeScript.Value
conf.IndexHtml = strings.Replace(conf.IndexHtml, "// customize-js", customizeScript.Value, 1)
}
davUsername, err := GetSettingByKey("WebDAV username")
if err == nil {
conf.DavUsername = davUsername.Value
}
davPassword, err := GetSettingByKey("WebDAV password")
if err == nil {
conf.DavPassword = davPassword.Value
}
}
+12
View File
@@ -0,0 +1,12 @@
package public
import "embed"
//go:embed *
var Public embed.FS
////go:embed index.html
//var Index embed.FS
//
////go:embed assets/**
//var Assets embed.FS
+94
View File
@@ -0,0 +1,94 @@
package server
import (
"fmt"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"strconv"
"time"
)
func GetAccounts(c *gin.Context) {
accounts, err := model.GetAccounts()
if err != nil {
ErrorResp(c, err, 500)
return
}
SuccessResp(c, accounts)
}
func CreateAccount(c *gin.Context) {
var req model.Account
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
return
}
driver, ok := drivers.GetDriver(req.Type)
if !ok {
ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
return
}
now := time.Now()
req.UpdatedAt = &now
if err := model.CreateAccount(&req); err != nil {
ErrorResp(c, err, 500)
} else {
log.Debugf("new account: %+v", req)
err = driver.Save(&req, nil)
if err != nil {
ErrorResp(c, err, 500)
return
}
SuccessResp(c)
}
}
func SaveAccount(c *gin.Context) {
var req model.Account
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
return
}
driver, ok := drivers.GetDriver(req.Type)
if !ok {
ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
return
}
old, err := model.GetAccountById(req.ID)
if err != nil {
ErrorResp(c, err, 400)
return
}
now := time.Now()
req.UpdatedAt = &now
if old.Name != req.Name {
model.DeleteAccountFromMap(old.Name)
}
if err := model.SaveAccount(&req); err != nil {
ErrorResp(c, err, 500)
} else {
log.Debugf("save account: %+v", req)
err = driver.Save(&req, old)
if err != nil {
ErrorResp(c, err, 500)
return
}
SuccessResp(c)
}
}
func DeleteAccount(c *gin.Context) {
idStr := c.Query("id")
id, err := strconv.Atoi(idStr)
if err != nil {
ErrorResp(c, err, 400)
return
}
if err := model.DeleteAccount(uint(id)); err != nil {
ErrorResp(c, err, 500)
return
}
SuccessResp(c)
}
+15
View File
@@ -0,0 +1,15 @@
package server
import (
"github.com/Xhofe/alist/conf"
"github.com/gin-gonic/gin"
)
func ClearCache(c *gin.Context) {
err := conf.Cache.Clear(conf.Ctx)
if err != nil {
ErrorResp(c, err, 500)
} else {
SuccessResp(c)
}
}
+80
View File
@@ -0,0 +1,80 @@
package server
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
"path/filepath"
)
func Auth(c *gin.Context) {
token := c.GetHeader("Authorization")
password, err := model.GetSettingByKey("password")
if err != nil {
if err == gorm.ErrRecordNotFound {
ErrorResp(c, fmt.Errorf("password not set"), 400)
return
}
ErrorResp(c, err, 500)
return
}
if token != utils.GetMD5Encode(password.Value) {
ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
c.Next()
}
func Login(c *gin.Context) {
SuccessResp(c)
}
func CheckAccount(c *gin.Context) {
if model.AccountsCount() == 0 {
ErrorResp(c, fmt.Errorf("no accounts,please add one first"), 1001)
return
}
c.Next()
}
func CheckParent(path string, password string) bool {
meta, err := model.GetMetaByPath(path)
if err == nil {
if meta.Password != "" && meta.Password != password {
return false
}
return true
} else {
if path == "/" || path == "\\" {
return true
}
return CheckParent(filepath.Dir(path), password)
}
}
func CheckDownLink(path string, passwordMd5 string) bool {
if !conf.CheckDown {
return true
}
meta, err := model.GetMetaByPath(path)
log.Debugf("check down path: %s", path)
if err == nil {
log.Debugf("check down link: %s,%s", meta.Password, passwordMd5)
if meta.Password != "" && utils.Get16MD5Encode(meta.Password) != passwordMd5 {
return false
}
return true
} else {
if !conf.CheckParent {
return true
}
if path == "/" || path == "\\" {
return true
}
return CheckDownLink(filepath.Dir(path), passwordMd5)
}
}
+66
View File
@@ -0,0 +1,66 @@
package server
import (
"fmt"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"strings"
)
type Resp struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
var path, name string
switch model.AccountsCount() {
case 0:
return nil, "", nil, fmt.Errorf("no accounts,please add one first")
case 1:
path = rawPath
break
default:
paths := strings.Split(rawPath, "/")
path = "/" + strings.Join(paths[2:], "/")
name = paths[1]
}
account, ok := model.GetAccount(name)
if !ok {
return nil, "", nil, fmt.Errorf("no [%s] account", name)
}
driver, ok := drivers.GetDriver(account.Type)
if !ok {
return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type)
}
return &account, path, driver, nil
}
func ErrorResp(c *gin.Context, err error, code int) {
log.Error(err.Error())
c.JSON(200, Resp{
Code: code,
Message: err.Error(),
Data: nil,
})
c.Abort()
}
func SuccessResp(c *gin.Context, data ...interface{}) {
if len(data) == 0 {
c.JSON(200, Resp{
Code: 200,
Message: "success",
Data: nil,
})
return
}
c.JSON(200, Resp{
Code: 200,
Message: "success",
Data: data[0],
})
}
-24
View File
@@ -1,24 +0,0 @@
package controllers
import "github.com/gin-gonic/gin"
// common meta response
func MetaResponse(code int, msg string) gin.H {
return gin.H{
"meta": gin.H{
"code": code,
"msg": msg,
},
}
}
// common data response
func DataResponse(data interface{}) gin.H {
return gin.H{
"meta": gin.H{
"code": 200,
"msg": "success",
},
"data": data,
}
}
-60
View File
@@ -1,60 +0,0 @@
package controllers
import (
"github.com/Xhofe/alist/alidrive"
"github.com/Xhofe/alist/server/models"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
"strings"
)
type DownReq struct {
Password string `form:"pw"`
}
// handle download request
func Down(c *gin.Context) {
filePath := c.Param("path")[1:]
var down DownReq
if err := c.ShouldBindQuery(&down); err != nil {
c.JSON(200, MetaResponse(400, "Bad Request."))
return
}
log.Debugf("down:%s", filePath)
dir, name := filepath.Split(filePath)
fileModel, err := models.GetFileByDirAndName(dir, name)
if err != nil {
if fileModel == nil {
c.JSON(200, MetaResponse(404, "Path not found."))
return
}
c.JSON(200, MetaResponse(500, err.Error()))
return
}
if fileModel.Password != "" && down.Password != utils.Get16MD5Encode(fileModel.Password) {
if down.Password == "" {
c.JSON(200, MetaResponse(401, "need password."))
} else {
c.JSON(200, MetaResponse(401, "wrong password."))
}
return
}
if fileModel.Type == "folder" {
c.JSON(200, MetaResponse(406, "无法下载目录."))
return
}
drive := utils.GetDriveByName(strings.Split(dir, "/")[0])
if drive == nil {
c.JSON(200, MetaResponse(500, "找不到drive."))
return
}
file, err := alidrive.GetDownLoadUrl(fileModel.FileId, drive)
if err != nil {
c.JSON(200, MetaResponse(500, err.Error()))
return
}
c.Redirect(301, file.Url)
return
}
-56
View File
@@ -1,56 +0,0 @@
package controllers
import (
"github.com/Xhofe/alist/alidrive"
"github.com/Xhofe/alist/server/models"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
"strings"
)
// get request bean
type GetReq struct {
Path string `json:"path" binding:"required"`
Password string `json:"password"`
}
// handle get request
func Get(c *gin.Context) {
var get GetReq
if err := c.ShouldBindJSON(&get); err != nil {
c.JSON(200, MetaResponse(400, "Bad Request:"+err.Error()))
return
}
log.Debugf("list:%+v", get)
dir, name := filepath.Split(get.Path)
file, err := models.GetFileByDirAndName(dir, name)
if err != nil {
if file == nil {
c.JSON(200, MetaResponse(404, "Path not found."))
return
}
c.JSON(200, MetaResponse(500, err.Error()))
return
}
if file.Password != "" && file.Password != get.Password {
if get.Password == "" {
c.JSON(200, MetaResponse(401, "need password."))
} else {
c.JSON(200, MetaResponse(401, "wrong password."))
}
return
}
drive := utils.GetDriveByName(strings.Split(dir, "/")[0])
if drive == nil {
c.JSON(200, MetaResponse(500, "找不到drive."))
return
}
down, err := alidrive.GetDownLoadUrl(file.FileId, drive)
if err != nil {
c.JSON(200, MetaResponse(500, err.Error()))
return
}
c.JSON(200, DataResponse(down))
}
-33
View File
@@ -1,33 +0,0 @@
package controllers
import (
"github.com/Xhofe/alist/alidrive"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
type OfficePreviewReq struct {
FileId string `json:"file_id" binding:"required"`
}
// handle office_preview request
func OfficePreview(c *gin.Context) {
drive := utils.GetDriveByName(c.Param("drive"))
if drive == nil {
c.JSON(200, MetaResponse(400, "drive isn't exist."))
return
}
var req OfficePreviewReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(200, MetaResponse(400, "Bad Request:"+err.Error()))
return
}
log.Debugf("preview_req:%+v", req)
preview, err := alidrive.GetOfficePreviewUrl(req.FileId, drive)
if err != nil {
c.JSON(200, MetaResponse(500, err.Error()))
return
}
c.JSON(200, DataResponse(preview))
}
-70
View File
@@ -1,70 +0,0 @@
package controllers
import (
"github.com/Xhofe/alist/server/models"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
)
// path request bean
type PathReq struct {
Path string `json:"path" binding:"required"`
Password string `json:"password"`
}
// handle path request
func Path(c *gin.Context) {
var req PathReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(200, MetaResponse(400, "Bad Request:"+err.Error()))
return
}
log.Debugf("path:%+v", req)
// find model
dir, name := filepath.Split(req.Path)
file, err := models.GetFileByDirAndName(dir, name)
if err != nil {
// folder model not exist
if file == nil {
c.JSON(200, MetaResponse(404, "path not found."))
return
}
c.JSON(200, MetaResponse(500, err.Error()))
return
}
// check password
if file.Password != "" && file.Password != req.Password {
if req.Password == "" {
c.JSON(200, MetaResponse(401, "need password."))
} else {
c.JSON(200, MetaResponse(401, "wrong password."))
}
return
}
// file
if file.Type == "file" {
if file.Password == "" {
file.Password = "n"
} else {
file.Password = "y"
}
c.JSON(200, DataResponse(file))
return
}
// folder
files, err := models.GetFilesByDir(req.Path + "/")
if err != nil {
c.JSON(200, MetaResponse(500, err.Error()))
return
}
// delete password
for i, _ := range *files {
if (*files)[i].Password == "" {
(*files)[i].Password = "n"
} else {
(*files)[i].Password = "y"
}
}
c.JSON(200, DataResponse(files))
}
-35
View File
@@ -1,35 +0,0 @@
package controllers
import (
"github.com/Xhofe/alist/server/models"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
type SearchReq struct {
Keyword string `json:"keyword" binding:"required"`
Dir string `json:"dir" binding:"required"`
}
func LocalSearch(c *gin.Context) {
var req SearchReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(200, MetaResponse(400, "Bad Request:"+err.Error()))
return
}
log.Debugf("list:%+v", req)
files, err := models.SearchByNameInDir(req.Keyword, req.Dir)
if err != nil {
if files == nil {
c.JSON(200, MetaResponse(404, "Path not found."))
return
}
c.JSON(200, MetaResponse(500, err.Error()))
return
}
c.JSON(200, DataResponse(files))
}
func GlobalSearch(c *gin.Context) {
}
-41
View File
@@ -1,41 +0,0 @@
package controllers
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/server/models"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
)
// handle info request
func Info(c *gin.Context) {
c.JSON(200, DataResponse(conf.Conf.Info))
}
// rebuild tree
func RebuildTree(c *gin.Context) {
drive := utils.GetDriveByName(c.Param("drive"))
if drive == nil {
c.JSON(200, MetaResponse(400, "drive isn't exist."))
return
}
password := c.Param("password")
if password != conf.Conf.Server.Password {
if password == "" {
c.JSON(200, MetaResponse(401, "need password."))
return
}
c.JSON(200, MetaResponse(401, "wrong password."))
return
}
if err := models.Clear(drive); err != nil {
c.JSON(200, MetaResponse(500, err.Error()))
return
}
if err := models.BuildTree(drive); err != nil {
c.JSON(200, MetaResponse(500, err.Error()))
return
}
c.JSON(200, MetaResponse(200, "success."))
return
}
+132
View File
@@ -0,0 +1,132 @@
package server
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http/httputil"
"net/url"
"path/filepath"
"strings"
)
func Down(c *gin.Context) {
rawPath, err := url.PathUnescape(c.Param("path"))
if err != nil {
ErrorResp(c, err, 500)
return
}
rawPath = utils.ParsePath(rawPath)
log.Debugf("down: %s", rawPath)
pw := c.Query("pw")
if !CheckDownLink(filepath.Dir(rawPath), pw) {
ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
account, path, driver, err := ParsePath(rawPath)
if err != nil {
ErrorResp(c, err, 500)
return
}
if account.Type == "GoogleDrive" {
Proxy(c)
return
}
link, err := driver.Link(path, account)
if err != nil {
ErrorResp(c, err, 500)
return
}
if account.Type == "Native" {
c.File(link)
return
} else {
c.Redirect(302, link)
return
}
}
func Proxy(c *gin.Context) {
rawPath, err := url.PathUnescape(c.Param("path"))
if err != nil {
ErrorResp(c, err, 500)
return
}
rawPath = utils.ParsePath(rawPath)
log.Debugf("proxy: %s", rawPath)
pw := c.Query("pw")
if !CheckDownLink(filepath.Dir(rawPath), pw) {
ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
account, path, driver, err := ParsePath(rawPath)
if err != nil {
ErrorResp(c, err, 500)
return
}
if !account.Proxy && utils.GetFileType(filepath.Ext(rawPath)) != conf.TEXT {
ErrorResp(c, fmt.Errorf("[%s] not allowed proxy", account.Name), 403)
return
}
link, err := driver.Link(path, account)
if err != nil {
ErrorResp(c, err, 500)
return
}
if account.Type == "Native" {
c.File(link)
return
} else {
if utils.GetFileType(filepath.Ext(rawPath)) == conf.TEXT {
Text(c, link)
return
}
driver.Proxy(c, account)
r := c.Request
w := c.Writer
target, err := url.Parse(link)
if err != nil {
ErrorResp(c, err, 500)
return
}
protocol := "http://"
if strings.HasPrefix(link, "https://") {
protocol = "https://"
}
targetHost, err := url.Parse(fmt.Sprintf("%s%s", protocol, target.Host))
proxy := httputil.NewSingleHostReverseProxy(targetHost)
r.URL = target
r.Host = target.Host
proxy.ServeHTTP(w, r)
}
}
var client *resty.Client
func init() {
client = resty.New()
client.SetRetryCount(3)
}
func Text(c *gin.Context, link string) {
res, err := client.R().Get(link)
if err != nil {
ErrorResp(c, err, 500)
return
}
text := res.String()
t := utils.GetStrCoding(res.Body())
log.Debugf("text type: %s", t)
if t != utils.UTF8 {
body, err := utils.GbkToUtf8(res.Body())
if err != nil {
ErrorResp(c, err, 500)
return
}
text = string(body)
}
c.String(200, text)
}
+10
View File
@@ -0,0 +1,10 @@
package server
import (
"github.com/Xhofe/alist/drivers"
"github.com/gin-gonic/gin"
)
func GetDrivers(c *gin.Context) {
SuccessResp(c, drivers.GetDrivers())
}
+60
View File
@@ -0,0 +1,60 @@
package server
import (
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"strconv"
)
func GetMetas(c *gin.Context) {
metas,err := model.GetMetas()
if err != nil {
ErrorResp(c,err,500)
return
}
SuccessResp(c, metas)
}
func CreateMeta(c *gin.Context) {
var req model.Meta
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
return
}
req.Path = utils.ParsePath(req.Path)
if err := model.CreateMeta(req); err != nil {
ErrorResp(c, err, 500)
} else {
SuccessResp(c)
}
}
func SaveMeta(c *gin.Context) {
var req model.Meta
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
return
}
req.Path = utils.ParsePath(req.Path)
if err := model.SaveMeta(req); err != nil {
ErrorResp(c, err, 500)
} else {
SuccessResp(c)
}
}
func DeleteMeta(c *gin.Context) {
idStr := c.Query("id")
id, err := strconv.Atoi(idStr)
if err != nil {
ErrorResp(c, err, 400)
return
}
//path = utils.ParsePath(path)
if err := model.DeleteMeta(uint(id)); err != nil {
ErrorResp(c, err, 500)
return
}
SuccessResp(c)
}
-37
View File
@@ -1,37 +0,0 @@
package server
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/server/controllers"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
)
// handle cors request
func CorsHandler() gin.HandlerFunc {
return func(context *gin.Context) {
origin := context.GetHeader("Origin")
// 同源
if origin == "" {
context.Next()
return
}
method := context.Request.Method
// 设置跨域
context.Header("Access-Control-Allow-Origin", origin)
context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
context.Header("Access-Control-Allow-Headers", "Content-Length,session,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language, Keep-Alive, User-Agent, Cache-Control, Content-Type")
context.Header("Access-Control-Expose-Headers", "Content-Length,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified")
context.Header("Access-Control-Max-Age", "172800")
// 信任域名
if conf.Conf.Server.SiteUrl != "*" && utils.ContainsString(conf.Origins, context.GetHeader("Origin")) == -1 {
context.JSON(200, controllers.MetaResponse(413, "The origin is not in the site_url list, please configure it correctly."))
context.Abort()
}
if method == "OPTIONS" {
context.AbortWithStatus(204)
}
//处理请求
context.Next()
}
}
-91
View File
@@ -1,91 +0,0 @@
package models
import (
"fmt"
"github.com/Xhofe/alist/alidrive"
"github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
"strings"
)
func BuildTreeAll() {
for i, _ := range conf.Conf.AliDrive.Drives {
if err := BuildTree(&conf.Conf.AliDrive.Drives[i]); err != nil {
log.Errorf("盘[%s]构建目录树失败:%s", err.Error())
} else {
log.Infof("盘[%s]构建目录树成功")
}
}
}
// build tree
func BuildTree(drive *conf.Drive) error {
log.Infof("开始构建目录树...")
tx := conf.DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
return err
}
rootFile := File{
Dir: "",
FileId: drive.RootFolder,
Name: drive.Name,
Type: "folder",
Password: drive.Password,
}
if err := tx.Create(&rootFile).Error; err != nil {
tx.Rollback()
return err
}
if err := BuildOne(drive.RootFolder, drive.Name+"/", tx, drive.Password, drive); err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
}
func BuildOne(parent string, path string, tx *gorm.DB, parentPassword string, drive *conf.Drive) error {
files, err := alidrive.GetList(parent, conf.Conf.AliDrive.MaxFilesCount, "", "", "", drive)
if err != nil {
return err
}
for _, file := range files.Items {
name := file.Name
if strings.HasSuffix(name, ".hide") {
continue
}
password := parentPassword
if strings.Contains(name, ".password-") {
index := strings.Index(name, ".password-")
name = file.Name[:index]
password = file.Name[index+10:]
}
newFile := File{
Dir: path,
FileExtension: file.FileExtension,
FileId: file.FileId,
Name: name,
Type: file.Type,
UpdatedAt: file.UpdatedAt,
Category: file.Category,
ContentType: file.ContentType,
Size: file.Size,
Password: password,
}
log.Debugf("插入file:%+v", newFile)
if err := tx.Create(&newFile).Error; err != nil {
return err
}
if file.Type == "folder" {
if err := BuildOne(file.FileId, fmt.Sprintf("%s%s/", path, name), tx, password, drive); err != nil {
return err
}
}
}
return nil
}
-64
View File
@@ -1,64 +0,0 @@
package models
import (
"fmt"
"github.com/Xhofe/alist/conf"
"time"
)
type File struct {
Dir string `json:"dir" gorm:"index"`
FileExtension string `json:"file_extension"`
FileId string `json:"file_id"`
Name string `json:"name" gorm:"index"`
Type string `json:"type"`
UpdatedAt *time.Time `json:"updated_at"`
Category string `json:"category"`
ContentType string `json:"content_type"`
Size int64 `json:"size"`
Password string `json:"password"`
Url string `json:"url" gorm:"-"`
}
func (file *File) Create() error {
return conf.DB.Create(file).Error
}
func Clear(drive *conf.Drive) error {
if err := conf.DB.Where("dir = '' AND name = ?", drive.Name).Delete(&File{}).Error; err != nil {
return err
}
return conf.DB.Where("dir like ?", fmt.Sprintf("%s%%", drive.Name)).Delete(&File{}).Error
}
func GetFileByDirAndName(dir, name string) (*File, error) {
var file File
if err := conf.DB.Where("dir = ? AND name = ?", dir, name).First(&file).Error; err != nil {
return nil, err
}
return &file, nil
}
func GetFilesByDir(dir string) (*[]File, error) {
var files []File
if err := conf.DB.Where("dir = ?", dir).Find(&files).Error; err != nil {
return nil, err
}
return &files, nil
}
func SearchByNameGlobal(keyword string) (*[]File, error) {
var files []File
if err := conf.DB.Where("name LIKE ? AND password = ''", fmt.Sprintf("%%%s%%", keyword)).Find(&files).Error; err != nil {
return nil, err
}
return &files, nil
}
func SearchByNameInDir(keyword string, dir string) (*[]File, error) {
var files []File
if err := conf.DB.Where("dir LIKE ? AND name LIKE ? AND password = ''", fmt.Sprintf("%s%%", dir), fmt.Sprintf("%%%s%%", keyword)).Find(&files).Error; err != nil {
return nil, err
}
return &files, nil
}
+143
View File
@@ -0,0 +1,143 @@
package server
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
"strings"
)
type PathReq struct {
Path string `json:"Path"`
Password string `json:"Password"`
}
func Path(c *gin.Context) {
var req PathReq
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
return
}
req.Path = utils.ParsePath(req.Path)
log.Debugf("path: %s", req.Path)
meta, err := model.GetMetaByPath(req.Path)
if err == nil {
if meta.Password != "" && meta.Password != req.Password {
ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
// TODO hide or ignore?
} else if conf.CheckParent {
if !CheckParent(filepath.Dir(req.Path), req.Password) {
ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
}
if model.AccountsCount() > 1 && req.Path == "/" {
files, err := model.GetAccountFiles()
if err != nil {
ErrorResp(c, err, 500)
return
}
c.JSON(200, Resp{
Code: 200,
Message: "folder",
Data: files,
})
return
}
account, path, driver, err := ParsePath(req.Path)
if err != nil {
ErrorResp(c, err, 500)
return
}
file, files, err := driver.Path(path, account)
if err != nil {
ErrorResp(c, err, 500)
return
}
if file != nil {
if account.Type == "Native" {
file.Url = fmt.Sprintf("//%s/d%s", c.Request.Host, req.Path)
}
c.JSON(200, Resp{
Code: 200,
Message: "file",
Data: []*model.File{file},
})
} else {
if meta != nil && meta.Hide != "" {
tmpFiles := make([]model.File, 0)
hideFiles := strings.Split(meta.Hide, ",")
for _, item := range files {
if !utils.IsContain(hideFiles, item.Name) {
tmpFiles = append(tmpFiles, item)
}
}
files = tmpFiles
}
c.JSON(200, Resp{
Code: 200,
Message: "folder",
Data: files,
})
}
}
func Link(c *gin.Context) {
var req PathReq
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
return
}
rawPath := req.Path
rawPath = utils.ParsePath(rawPath)
log.Debugf("link: %s", rawPath)
account, path, driver, err := ParsePath(rawPath)
if err != nil {
ErrorResp(c, err, 500)
return
}
link, err := driver.Link(path, account)
if err != nil {
ErrorResp(c, err, 500)
return
}
if account.Type == "Native" {
SuccessResp(c, gin.H{
"url": fmt.Sprintf("//%s/d%s", c.Request.Host, req.Path),
})
return
} else {
SuccessResp(c, gin.H{
"url": link,
})
return
}
}
func Preview(c *gin.Context) {
var req PathReq
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
return
}
rawPath := req.Path
rawPath = utils.ParsePath(rawPath)
log.Debugf("preview: %s", rawPath)
account, path, driver, err := ParsePath(rawPath)
if err != nil {
ErrorResp(c, err, 500)
return
}
data, err := driver.Preview(path, account)
if err != nil {
ErrorResp(c, err, 500)
} else {
SuccessResp(c, data)
}
}
+40 -24
View File
@@ -1,35 +1,51 @@
package server package server
import ( import (
"github.com/Xhofe/alist/conf" "github.com/gin-contrib/cors"
"github.com/Xhofe/alist/server/controllers"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
) )
// init router func InitApiRouter(r *gin.Engine) {
func InitRouter(engine *gin.Engine) {
log.Infof("初始化路由...") // TODO from settings
engine.Use(CorsHandler()) Cors(r)
engine.Use(static.Serve("/", static.LocalFile(conf.Conf.Server.Static, false))) r.GET("/d/*path", Down)
engine.NoRoute(func(c *gin.Context) { r.GET("/p/*path", Proxy)
c.File(conf.Conf.Server.Static + "/index.html")
}) api := r.Group("/api")
InitApiRouter(engine) public := api.Group("/public")
{
public.POST("/path", CheckAccount, Path)
public.POST("/preview", CheckAccount, Preview)
public.GET("/settings", GetSettingsPublic)
public.POST("/link", CheckAccount, Link)
} }
// init api router admin := api.Group("/admin")
func InitApiRouter(engine *gin.Engine) {
apiV2 := engine.Group("/api")
{ {
apiV2.GET("/info", controllers.Info) admin.Use(Auth)
apiV2.POST("/get", controllers.Get) admin.GET("/login", Login)
apiV2.POST("/path", controllers.Path) admin.GET("/settings", GetSettings)
apiV2.POST("/office_preview/:drive", controllers.OfficePreview) admin.POST("/settings", SaveSettings)
apiV2.POST("/local_search", controllers.LocalSearch) admin.POST("/account/create", CreateAccount)
apiV2.POST("/global_search", controllers.GlobalSearch) admin.POST("/account/save", SaveAccount)
apiV2.GET("/rebuild/:drive/:password", controllers.RebuildTree) admin.GET("/accounts", GetAccounts)
admin.DELETE("/account", DeleteAccount)
admin.GET("/drivers", GetDrivers)
admin.GET("/clear_cache", ClearCache)
admin.GET("/metas", GetMetas)
admin.POST("/meta/create", CreateMeta)
admin.POST("/meta/save", SaveMeta)
admin.DELETE("/meta", DeleteMeta)
} }
engine.GET("/d/*path", controllers.Down) Static(r)
WebDav(r)
}
func Cors(r *gin.Engine) {
config := cors.DefaultConfig()
config.AllowAllOrigins = true
config.AllowHeaders = append(config.AllowHeaders, "Authorization")
r.Use(cors.New(config))
} }
+38
View File
@@ -0,0 +1,38 @@
package server
import (
"github.com/Xhofe/alist/model"
"github.com/gin-gonic/gin"
)
func SaveSettings(c *gin.Context) {
var req []model.SettingItem
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
return
}
if err := model.SaveSettings(req); err != nil {
ErrorResp(c, err, 500)
} else {
model.LoadSettings()
SuccessResp(c)
}
}
func GetSettings(c *gin.Context) {
settings, err := model.GetSettings()
if err != nil {
ErrorResp(c, err, 400)
return
}
SuccessResp(c, settings)
}
func GetSettingsPublic(c *gin.Context) {
settings, err := model.GetSettingsPublic()
if err != nil {
ErrorResp(c, err, 400)
return
}
SuccessResp(c, settings)
}
+37
View File
@@ -0,0 +1,37 @@
package server
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/public"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"io/fs"
"io/ioutil"
"net/http"
)
func init() {
index, err := public.Public.Open("index.html")
if err != nil {
log.Errorf(err.Error())
return
}
data, _ := ioutil.ReadAll(index)
conf.RawIndexHtml = string(data)
}
func Static(r *gin.Engine) {
assets, err := fs.Sub(public.Public, "assets")
if err != nil {
log.Fatalf("can't find assets folder")
}
r.StaticFS("/assets/", http.FS(assets))
r.NoRoute(func(c *gin.Context) {
c.Status(200)
c.Header("Content-Type", "text/html")
_, _ = c.Writer.WriteString(conf.IndexHtml)
c.Writer.Flush()
c.Writer.WriteHeaderNow()
})
}
+60
View File
@@ -0,0 +1,60 @@
package server
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/server/webdav"
"github.com/gin-gonic/gin"
"net/http"
)
var handler *webdav.Handler
func init() {
handler = &webdav.Handler{
Prefix: "/dav",
LockSystem: webdav.NewMemLS(),
}
}
func WebDav(r *gin.Engine) {
dav := r.Group("/dav")
dav.Use(WebDAVAuth)
dav.Any("/*path", ServeWebDAV)
dav.Any("", ServeWebDAV)
dav.Handle("PROPFIND", "/*path", ServeWebDAV)
dav.Handle("PROPFIND", "", ServeWebDAV)
dav.Handle("MKCOL", "/*path", ServeWebDAV)
dav.Handle("LOCK", "/*path", ServeWebDAV)
dav.Handle("UNLOCK", "/*path", ServeWebDAV)
dav.Handle("PROPPATCH", "/*path", ServeWebDAV)
dav.Handle("COPY", "/*path", ServeWebDAV)
dav.Handle("MOVE", "/*path", ServeWebDAV)
}
func ServeWebDAV(c *gin.Context) {
fs := webdav.FileSystem{}
handler.ServeHTTP(c.Writer,c.Request,&fs)
}
func WebDAVAuth(c *gin.Context) {
if c.Request.Method == "OPTIONS" {
c.Next()
return
}
username, password, ok := c.Request.BasicAuth()
if !ok {
c.Writer.Header()["WWW-Authenticate"] = []string{`Basic realm="alist"`}
c.Status(http.StatusUnauthorized)
c.Abort()
return
}
if conf.DavUsername != "" && conf.DavUsername != username {
c.Status(http.StatusUnauthorized)
c.Abort()
}
if conf.DavPassword != "" && conf.DavPassword != password {
c.Status(http.StatusUnauthorized)
c.Abort()
}
c.Next()
}
+195
View File
@@ -0,0 +1,195 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package webdav
import (
"context"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"net/http"
"path"
"path/filepath"
"strings"
"time"
)
type FileSystem struct{}
func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
var path, name string
switch model.AccountsCount() {
case 0:
return nil, "", nil, fmt.Errorf("no accounts,please add one first")
case 1:
path = rawPath
break
default:
paths := strings.Split(rawPath, "/")
path = "/" + strings.Join(paths[2:], "/")
name = paths[1]
}
account, ok := model.GetAccount(name)
if !ok {
return nil, "", nil, fmt.Errorf("no [%s] account", name)
}
driver, ok := drivers.GetDriver(account.Type)
if !ok {
return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type)
}
return &account, path, driver, nil
}
func (fs *FileSystem) File(rawPath string) (*model.File, error) {
rawPath = utils.ParsePath(rawPath)
if model.AccountsCount() > 1 && rawPath == "/" {
now := time.Now()
return &model.File{
Name: "root",
Size: 0,
Type: conf.FOLDER,
Driver: "root",
UpdatedAt: &now,
}, nil
}
account, path_, driver, err := ParsePath(rawPath)
if err != nil {
return nil, err
}
return driver.File(path_, account)
}
func (fs *FileSystem) Files(rawPath string) ([]model.File, error) {
rawPath = utils.ParsePath(rawPath)
if model.AccountsCount() > 1 && rawPath == "/" {
files, err := model.GetAccountFiles()
if err != nil {
return nil, err
}
return files, nil
}
account, path_, driver, err := ParsePath(rawPath)
if err != nil {
return nil, err
}
return driver.Files(path_, account)
}
func GetPW(path string) string {
if !conf.CheckDown {
return ""
}
meta, err := model.GetMetaByPath(path)
if err == nil {
if meta.Password != "" {
utils.Get16MD5Encode(meta.Password)
}
return ""
} else {
if !conf.CheckParent {
return ""
}
if path == "/" || path == "\\" {
return ""
}
return GetPW(filepath.Dir(path))
}
}
func (fs *FileSystem) Link(host, rawPath string) (string, error) {
rawPath = utils.ParsePath(rawPath)
if model.AccountsCount() > 1 && rawPath == "/" {
// error
}
account, path_, driver, err := ParsePath(rawPath)
if err != nil {
return "", err
}
if account.Type == "Native" || account.Type == "GoogleDrive" {
link := fmt.Sprintf("//%s/p%s", host, rawPath)
if conf.CheckDown {
pw := GetPW(filepath.Dir(rawPath))
link += "?pw" + pw
}
log.Debugf("proxy link: %s", link)
return link, nil
}
return driver.Link(path_, account)
}
func (fs *FileSystem) CreateDirectory(ctx context.Context, reqPath string) (interface{}, error) {
return nil, nil
}
// slashClean is equivalent to but slightly more efficient than
// path.Clean("/" + name).
func slashClean(name string) string {
if name == "" || name[0] != '/' {
name = "/" + name
}
return path.Clean(name)
}
// moveFiles moves files and/or directories from src to dst.
//
// See section 9.9.4 for when various HTTP status codes apply.
func moveFiles(ctx context.Context, fs *FileSystem, src FileInfo, dst string, overwrite bool) (status int, err error) {
return http.StatusNoContent, nil
}
// copyFiles copies files and/or directories from src to dst.
//
// See section 9.8.5 for when various HTTP status codes apply.
func copyFiles(ctx context.Context, fs *FileSystem, src FileInfo, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
return http.StatusNoContent, nil
}
// walkFS traverses filesystem fs starting at name up to depth levels.
//
// Allowed values for depth are 0, 1 or infiniteDepth. For each visited node,
// walkFS calls walkFn. If a visited file system node is a directory and
// walkFn returns filepath.SkipDir, walkFS will skip traversal of this node.
func walkFS(
ctx context.Context,
fs *FileSystem,
depth int,
name string,
info FileInfo,
walkFn func(reqPath string, info FileInfo, err error) error) error {
// This implementation is based on Walk's code in the standard path/filepath package.
err := walkFn(name, info, nil)
if err != nil {
if info.IsDir() && err == filepath.SkipDir {
return nil
}
return err
}
if !info.IsDir() || depth == 0 {
return nil
}
if depth == 1 {
depth = 0
}
files, err := fs.Files(name)
if err != nil {
return err
}
for _, fileInfo := range files {
filename := path.Join(name, fileInfo.Name)
err = walkFS(ctx, fs, depth, filename, &fileInfo, walkFn)
if err != nil {
if !fileInfo.IsDir() || err != filepath.SkipDir {
return err
}
}
}
return nil
}
+173
View File
@@ -0,0 +1,173 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package webdav
// The If header is covered by Section 10.4.
// http://www.webdav.org/specs/rfc4918.html#HEADER_If
import (
"strings"
)
// ifHeader is a disjunction (OR) of ifLists.
type ifHeader struct {
lists []ifList
}
// ifList is a conjunction (AND) of Conditions, and an optional resource tag.
type ifList struct {
resourceTag string
conditions []Condition
}
// parseIfHeader parses the "If: foo bar" HTTP header. The httpHeader string
// should omit the "If:" prefix and have any "\r\n"s collapsed to a " ", as is
// returned by req.Header.Get("If") for a http.Request req.
func parseIfHeader(httpHeader string) (h ifHeader, ok bool) {
s := strings.TrimSpace(httpHeader)
switch tokenType, _, _ := lex(s); tokenType {
case '(':
return parseNoTagLists(s)
case angleTokenType:
return parseTaggedLists(s)
default:
return ifHeader{}, false
}
}
func parseNoTagLists(s string) (h ifHeader, ok bool) {
for {
l, remaining, ok := parseList(s)
if !ok {
return ifHeader{}, false
}
h.lists = append(h.lists, l)
if remaining == "" {
return h, true
}
s = remaining
}
}
func parseTaggedLists(s string) (h ifHeader, ok bool) {
resourceTag, n := "", 0
for first := true; ; first = false {
tokenType, tokenStr, remaining := lex(s)
switch tokenType {
case angleTokenType:
if !first && n == 0 {
return ifHeader{}, false
}
resourceTag, n = tokenStr, 0
s = remaining
case '(':
n++
l, remaining, ok := parseList(s)
if !ok {
return ifHeader{}, false
}
l.resourceTag = resourceTag
h.lists = append(h.lists, l)
if remaining == "" {
return h, true
}
s = remaining
default:
return ifHeader{}, false
}
}
}
func parseList(s string) (l ifList, remaining string, ok bool) {
tokenType, _, s := lex(s)
if tokenType != '(' {
return ifList{}, "", false
}
for {
tokenType, _, remaining = lex(s)
if tokenType == ')' {
if len(l.conditions) == 0 {
return ifList{}, "", false
}
return l, remaining, true
}
c, remaining, ok := parseCondition(s)
if !ok {
return ifList{}, "", false
}
l.conditions = append(l.conditions, c)
s = remaining
}
}
func parseCondition(s string) (c Condition, remaining string, ok bool) {
tokenType, tokenStr, s := lex(s)
if tokenType == notTokenType {
c.Not = true
tokenType, tokenStr, s = lex(s)
}
switch tokenType {
case strTokenType, angleTokenType:
c.Token = tokenStr
case squareTokenType:
c.ETag = tokenStr
default:
return Condition{}, "", false
}
return c, s, true
}
// Single-rune tokens like '(' or ')' have a token type equal to their rune.
// All other tokens have a negative token type.
const (
errTokenType = rune(-1)
eofTokenType = rune(-2)
strTokenType = rune(-3)
notTokenType = rune(-4)
angleTokenType = rune(-5)
squareTokenType = rune(-6)
)
func lex(s string) (tokenType rune, tokenStr string, remaining string) {
// The net/textproto Data that parses the HTTP header will collapse
// Linear White Space that spans multiple "\r\n" lines to a single " ",
// so we don't need to look for '\r' or '\n'.
for len(s) > 0 && (s[0] == '\t' || s[0] == ' ') {
s = s[1:]
}
if len(s) == 0 {
return eofTokenType, "", ""
}
i := 0
loop:
for ; i < len(s); i++ {
switch s[i] {
case '\t', ' ', '(', ')', '<', '>', '[', ']':
break loop
}
}
if i != 0 {
tokenStr, remaining = s[:i], s[i:]
if tokenStr == "Not" {
return notTokenType, "", remaining
}
return strTokenType, tokenStr, remaining
}
j := 0
switch s[0] {
case '<':
j, tokenType = strings.IndexByte(s, '>'), angleTokenType
case '[':
j, tokenType = strings.IndexByte(s, ']'), squareTokenType
default:
return rune(s[0]), "", s[1:]
}
if j < 0 {
return errTokenType, "", ""
}
return tokenType, s[1:j], s[j+1:]
}
+11
View File
@@ -0,0 +1,11 @@
This is a fork of the encoding/xml package at ca1d6c4, the last commit before
https://go.googlesource.com/go/+/c0d6d33 "encoding/xml: restore Go 1.4 name
space behavior" made late in the lead-up to the Go 1.5 release.
The list of encoding/xml changes is at
https://go.googlesource.com/go/+log/master/src/encoding/xml
This fork is temporary, and I (nigeltao) expect to revert it after Go 1.6 is
released.
See http://golang.org/issue/11841
File diff suppressed because it is too large Load Diff
+692
View File
@@ -0,0 +1,692 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xml
import (
"bytes"
"encoding"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
)
// BUG(rsc): Mapping between XML elements and data structures is inherently flawed:
// an XML element is an order-dependent collection of anonymous
// values, while a data structure is an order-independent collection
// of named values.
// See package json for a textual representation more suitable
// to data structures.
// Unmarshal parses the XML-encoded data and stores the result in
// the value pointed to by v, which must be an arbitrary struct,
// slice, or string. Well-formed data that does not fit into v is
// discarded.
//
// Because Unmarshal uses the reflect package, it can only assign
// to exported (upper case) fields. Unmarshal uses a case-sensitive
// comparison to match XML element names to tag values and struct
// field names.
//
// Unmarshal maps an XML element to a struct using the following rules.
// In the rules, the tag of a field refers to the value associated with the
// key 'xml' in the struct field's tag (see the example above).
//
// * If the struct has a field of type []byte or string with tag
// ",innerxml", Unmarshal accumulates the raw XML nested inside the
// element in that field. The rest of the rules still apply.
//
// * If the struct has a field named XMLName of type xml.Name,
// Unmarshal records the element name in that field.
//
// * If the XMLName field has an associated tag of the form
// "name" or "namespace-URL name", the XML element must have
// the given name (and, optionally, name space) or else Unmarshal
// returns an error.
//
// * If the XML element has an attribute whose name matches a
// struct field name with an associated tag containing ",attr" or
// the explicit name in a struct field tag of the form "name,attr",
// Unmarshal records the attribute value in that field.
//
// * If the XML element contains character data, that data is
// accumulated in the first struct field that has tag ",chardata".
// The struct field may have type []byte or string.
// If there is no such field, the character data is discarded.
//
// * If the XML element contains comments, they are accumulated in
// the first struct field that has tag ",comment". The struct
// field may have type []byte or string. If there is no such
// field, the comments are discarded.
//
// * If the XML element contains a sub-element whose name matches
// the prefix of a tag formatted as "a" or "a>b>c", unmarshal
// will descend into the XML structure looking for elements with the
// given names, and will map the innermost elements to that struct
// field. A tag starting with ">" is equivalent to one starting
// with the field name followed by ">".
//
// * If the XML element contains a sub-element whose name matches
// a struct field's XMLName tag and the struct field has no
// explicit name tag as per the previous rule, unmarshal maps
// the sub-element to that struct field.
//
// * If the XML element contains a sub-element whose name matches a
// field without any mode flags (",attr", ",chardata", etc), Unmarshal
// maps the sub-element to that struct field.
//
// * If the XML element contains a sub-element that hasn't matched any
// of the above rules and the struct has a field with tag ",any",
// unmarshal maps the sub-element to that struct field.
//
// * An anonymous struct field is handled as if the fields of its
// value were part of the outer struct.
//
// * A struct field with tag "-" is never unmarshalled into.
//
// Unmarshal maps an XML element to a string or []byte by saving the
// concatenation of that element's character data in the string or
// []byte. The saved []byte is never nil.
//
// Unmarshal maps an attribute value to a string or []byte by saving
// the value in the string or slice.
//
// Unmarshal maps an XML element to a slice by extending the length of
// the slice and mapping the element to the newly created value.
//
// Unmarshal maps an XML element or attribute value to a bool by
// setting it to the boolean value represented by the string.
//
// Unmarshal maps an XML element or attribute value to an integer or
// floating-point field by setting the field to the result of
// interpreting the string value in decimal. There is no check for
// overflow.
//
// Unmarshal maps an XML element to an xml.Name by recording the
// element name.
//
// Unmarshal maps an XML element to a pointer by setting the pointer
// to a freshly allocated value and then mapping the element to that value.
//
func Unmarshal(data []byte, v interface{}) error {
return NewDecoder(bytes.NewReader(data)).Decode(v)
}
// Decode works like xml.Unmarshal, except it reads the decoder
// stream to find the start element.
func (d *Decoder) Decode(v interface{}) error {
return d.DecodeElement(v, nil)
}
// DecodeElement works like xml.Unmarshal except that it takes
// a pointer to the start XML element to decode into v.
// It is useful when a client reads some raw XML tokens itself
// but also wants to defer to Unmarshal for some elements.
func (d *Decoder) DecodeElement(v interface{}, start *StartElement) error {
val := reflect.ValueOf(v)
if val.Kind() != reflect.Ptr {
return errors.New("non-pointer passed to Unmarshal")
}
return d.unmarshal(val.Elem(), start)
}
// An UnmarshalError represents an error in the unmarshalling process.
type UnmarshalError string
func (e UnmarshalError) Error() string { return string(e) }
// Unmarshaler is the interface implemented by objects that can unmarshal
// an XML element description of themselves.
//
// UnmarshalXML decodes a single XML element
// beginning with the given start element.
// If it returns an error, the outer call to Unmarshal stops and
// returns that error.
// UnmarshalXML must consume exactly one XML element.
// One common implementation strategy is to unmarshal into
// a separate value with a layout matching the expected XML
// using d.DecodeElement, and then to copy the data from
// that value into the receiver.
// Another common strategy is to use d.Token to process the
// XML object one token at a time.
// UnmarshalXML may not use d.RawToken.
type Unmarshaler interface {
UnmarshalXML(d *Decoder, start StartElement) error
}
// UnmarshalerAttr is the interface implemented by objects that can unmarshal
// an XML attribute description of themselves.
//
// UnmarshalXMLAttr decodes a single XML attribute.
// If it returns an error, the outer call to Unmarshal stops and
// returns that error.
// UnmarshalXMLAttr is used only for struct fields with the
// "attr" option in the field tag.
type UnmarshalerAttr interface {
UnmarshalXMLAttr(attr Attr) error
}
// receiverType returns the receiver type to use in an expression like "%s.MethodName".
func receiverType(val interface{}) string {
t := reflect.TypeOf(val)
if t.Name() != "" {
return t.String()
}
return "(" + t.String() + ")"
}
// unmarshalInterface unmarshals a single XML element into val.
// start is the opening tag of the element.
func (p *Decoder) unmarshalInterface(val Unmarshaler, start *StartElement) error {
// Record that decoder must stop at end tag corresponding to start.
p.pushEOF()
p.unmarshalDepth++
err := val.UnmarshalXML(p, *start)
p.unmarshalDepth--
if err != nil {
p.popEOF()
return err
}
if !p.popEOF() {
return fmt.Errorf("xml: %s.UnmarshalXML did not consume entire <%s> element", receiverType(val), start.Name.Local)
}
return nil
}
// unmarshalTextInterface unmarshals a single XML element into val.
// The chardata contained in the element (but not its children)
// is passed to the text unmarshaler.
func (p *Decoder) unmarshalTextInterface(val encoding.TextUnmarshaler, start *StartElement) error {
var buf []byte
depth := 1
for depth > 0 {
t, err := p.Token()
if err != nil {
return err
}
switch t := t.(type) {
case CharData:
if depth == 1 {
buf = append(buf, t...)
}
case StartElement:
depth++
case EndElement:
depth--
}
}
return val.UnmarshalText(buf)
}
// unmarshalAttr unmarshals a single XML attribute into val.
func (p *Decoder) unmarshalAttr(val reflect.Value, attr Attr) error {
if val.Kind() == reflect.Ptr {
if val.IsNil() {
val.Set(reflect.New(val.Type().Elem()))
}
val = val.Elem()
}
if val.CanInterface() && val.Type().Implements(unmarshalerAttrType) {
// This is an unmarshaler with a non-pointer receiver,
// so it's likely to be incorrect, but we do what we're told.
return val.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr)
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(unmarshalerAttrType) {
return pv.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr)
}
}
// Not an UnmarshalerAttr; try encoding.TextUnmarshaler.
if val.CanInterface() && val.Type().Implements(textUnmarshalerType) {
// This is an unmarshaler with a non-pointer receiver,
// so it's likely to be incorrect, but we do what we're told.
return val.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value))
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {
return pv.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value))
}
}
copyValue(val, []byte(attr.Value))
return nil
}
var (
unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
unmarshalerAttrType = reflect.TypeOf((*UnmarshalerAttr)(nil)).Elem()
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
)
// Unmarshal a single XML element into val.
func (p *Decoder) unmarshal(val reflect.Value, start *StartElement) error {
// Find start element if we need it.
if start == nil {
for {
tok, err := p.Token()
if err != nil {
return err
}
if t, ok := tok.(StartElement); ok {
start = &t
break
}
}
}
// Load value from interface, but only if the result will be
// usefully addressable.
if val.Kind() == reflect.Interface && !val.IsNil() {
e := val.Elem()
if e.Kind() == reflect.Ptr && !e.IsNil() {
val = e
}
}
if val.Kind() == reflect.Ptr {
if val.IsNil() {
val.Set(reflect.New(val.Type().Elem()))
}
val = val.Elem()
}
if val.CanInterface() && val.Type().Implements(unmarshalerType) {
// This is an unmarshaler with a non-pointer receiver,
// so it's likely to be incorrect, but we do what we're told.
return p.unmarshalInterface(val.Interface().(Unmarshaler), start)
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(unmarshalerType) {
return p.unmarshalInterface(pv.Interface().(Unmarshaler), start)
}
}
if val.CanInterface() && val.Type().Implements(textUnmarshalerType) {
return p.unmarshalTextInterface(val.Interface().(encoding.TextUnmarshaler), start)
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {
return p.unmarshalTextInterface(pv.Interface().(encoding.TextUnmarshaler), start)
}
}
var (
data []byte
saveData reflect.Value
comment []byte
saveComment reflect.Value
saveXML reflect.Value
saveXMLIndex int
saveXMLData []byte
saveAny reflect.Value
sv reflect.Value
tinfo *typeInfo
err error
)
switch v := val; v.Kind() {
default:
return errors.New("unknown type " + v.Type().String())
case reflect.Interface:
// TODO: For now, simply ignore the field. In the near
// future we may choose to unmarshal the start
// element on it, if not nil.
return p.Skip()
case reflect.Slice:
typ := v.Type()
if typ.Elem().Kind() == reflect.Uint8 {
// []byte
saveData = v
break
}
// Slice of element values.
// Grow slice.
n := v.Len()
if n >= v.Cap() {
ncap := 2 * n
if ncap < 4 {
ncap = 4
}
new := reflect.MakeSlice(typ, n, ncap)
reflect.Copy(new, v)
v.Set(new)
}
v.SetLen(n + 1)
// Recur to read element into slice.
if err := p.unmarshal(v.Index(n), start); err != nil {
v.SetLen(n)
return err
}
return nil
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.String:
saveData = v
case reflect.Struct:
typ := v.Type()
if typ == nameType {
v.Set(reflect.ValueOf(start.Name))
break
}
sv = v
tinfo, err = getTypeInfo(typ)
if err != nil {
return err
}
// Validate and assign element name.
if tinfo.xmlname != nil {
finfo := tinfo.xmlname
if finfo.name != "" && finfo.name != start.Name.Local {
return UnmarshalError("expected element type <" + finfo.name + "> but have <" + start.Name.Local + ">")
}
if finfo.xmlns != "" && finfo.xmlns != start.Name.Space {
e := "expected element <" + finfo.name + "> in name space " + finfo.xmlns + " but have "
if start.Name.Space == "" {
e += "no name space"
} else {
e += start.Name.Space
}
return UnmarshalError(e)
}
fv := finfo.value(sv)
if _, ok := fv.Interface().(Name); ok {
fv.Set(reflect.ValueOf(start.Name))
}
}
// Assign attributes.
// Also, determine whether we need to save character data or comments.
for i := range tinfo.fields {
finfo := &tinfo.fields[i]
switch finfo.flags & fMode {
case fAttr:
strv := finfo.value(sv)
// Look for attribute.
for _, a := range start.Attr {
if a.Name.Local == finfo.name && (finfo.xmlns == "" || finfo.xmlns == a.Name.Space) {
if err := p.unmarshalAttr(strv, a); err != nil {
return err
}
break
}
}
case fCharData:
if !saveData.IsValid() {
saveData = finfo.value(sv)
}
case fComment:
if !saveComment.IsValid() {
saveComment = finfo.value(sv)
}
case fAny, fAny | fElement:
if !saveAny.IsValid() {
saveAny = finfo.value(sv)
}
case fInnerXml:
if !saveXML.IsValid() {
saveXML = finfo.value(sv)
if p.saved == nil {
saveXMLIndex = 0
p.saved = new(bytes.Buffer)
} else {
saveXMLIndex = p.savedOffset()
}
}
}
}
}
// Find end element.
// Process sub-elements along the way.
Loop:
for {
var savedOffset int
if saveXML.IsValid() {
savedOffset = p.savedOffset()
}
tok, err := p.Token()
if err != nil {
return err
}
switch t := tok.(type) {
case StartElement:
consumed := false
if sv.IsValid() {
consumed, err = p.unmarshalPath(tinfo, sv, nil, &t)
if err != nil {
return err
}
if !consumed && saveAny.IsValid() {
consumed = true
if err := p.unmarshal(saveAny, &t); err != nil {
return err
}
}
}
if !consumed {
if err := p.Skip(); err != nil {
return err
}
}
case EndElement:
if saveXML.IsValid() {
saveXMLData = p.saved.Bytes()[saveXMLIndex:savedOffset]
if saveXMLIndex == 0 {
p.saved = nil
}
}
break Loop
case CharData:
if saveData.IsValid() {
data = append(data, t...)
}
case Comment:
if saveComment.IsValid() {
comment = append(comment, t...)
}
}
}
if saveData.IsValid() && saveData.CanInterface() && saveData.Type().Implements(textUnmarshalerType) {
if err := saveData.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil {
return err
}
saveData = reflect.Value{}
}
if saveData.IsValid() && saveData.CanAddr() {
pv := saveData.Addr()
if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {
if err := pv.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil {
return err
}
saveData = reflect.Value{}
}
}
if err := copyValue(saveData, data); err != nil {
return err
}
switch t := saveComment; t.Kind() {
case reflect.String:
t.SetString(string(comment))
case reflect.Slice:
t.Set(reflect.ValueOf(comment))
}
switch t := saveXML; t.Kind() {
case reflect.String:
t.SetString(string(saveXMLData))
case reflect.Slice:
t.Set(reflect.ValueOf(saveXMLData))
}
return nil
}
func copyValue(dst reflect.Value, src []byte) (err error) {
dst0 := dst
if dst.Kind() == reflect.Ptr {
if dst.IsNil() {
dst.Set(reflect.New(dst.Type().Elem()))
}
dst = dst.Elem()
}
// Save accumulated data.
switch dst.Kind() {
case reflect.Invalid:
// Probably a comment.
default:
return errors.New("cannot unmarshal into " + dst0.Type().String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
itmp, err := strconv.ParseInt(string(src), 10, dst.Type().Bits())
if err != nil {
return err
}
dst.SetInt(itmp)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
utmp, err := strconv.ParseUint(string(src), 10, dst.Type().Bits())
if err != nil {
return err
}
dst.SetUint(utmp)
case reflect.Float32, reflect.Float64:
ftmp, err := strconv.ParseFloat(string(src), dst.Type().Bits())
if err != nil {
return err
}
dst.SetFloat(ftmp)
case reflect.Bool:
value, err := strconv.ParseBool(strings.TrimSpace(string(src)))
if err != nil {
return err
}
dst.SetBool(value)
case reflect.String:
dst.SetString(string(src))
case reflect.Slice:
if len(src) == 0 {
// non-nil to flag presence
src = []byte{}
}
dst.SetBytes(src)
}
return nil
}
// unmarshalPath walks down an XML structure looking for wanted
// paths, and calls unmarshal on them.
// The consumed result tells whether XML elements have been consumed
// from the Decoder until start's matching end element, or if it's
// still untouched because start is uninteresting for sv's fields.
func (p *Decoder) unmarshalPath(tinfo *typeInfo, sv reflect.Value, parents []string, start *StartElement) (consumed bool, err error) {
recurse := false
Loop:
for i := range tinfo.fields {
finfo := &tinfo.fields[i]
if finfo.flags&fElement == 0 || len(finfo.parents) < len(parents) || finfo.xmlns != "" && finfo.xmlns != start.Name.Space {
continue
}
for j := range parents {
if parents[j] != finfo.parents[j] {
continue Loop
}
}
if len(finfo.parents) == len(parents) && finfo.name == start.Name.Local {
// It's a perfect match, unmarshal the field.
return true, p.unmarshal(finfo.value(sv), start)
}
if len(finfo.parents) > len(parents) && finfo.parents[len(parents)] == start.Name.Local {
// It's a prefix for the field. Break and recurse
// since it's not ok for one field path to be itself
// the prefix for another field path.
recurse = true
// We can reuse the same slice as long as we
// don't try to append to it.
parents = finfo.parents[:len(parents)+1]
break
}
}
if !recurse {
// We have no business with this element.
return false, nil
}
// The element is not a perfect match for any field, but one
// or more fields have the path to this element as a parent
// prefix. Recurse and attempt to match these.
for {
var tok Token
tok, err = p.Token()
if err != nil {
return true, err
}
switch t := tok.(type) {
case StartElement:
consumed2, err := p.unmarshalPath(tinfo, sv, parents, &t)
if err != nil {
return true, err
}
if !consumed2 {
if err := p.Skip(); err != nil {
return true, err
}
}
case EndElement:
return true, nil
}
}
}
// Skip reads tokens until it has consumed the end element
// matching the most recent start element already consumed.
// It recurs if it encounters a start element, so it can be used to
// skip nested structures.
// It returns nil if it finds an end element matching the start
// element; otherwise it returns an error describing the problem.
func (d *Decoder) Skip() error {
for {
tok, err := d.Token()
if err != nil {
return err
}
switch tok.(type) {
case StartElement:
if err := d.Skip(); err != nil {
return err
}
case EndElement:
return nil
}
}
}
+371
View File
@@ -0,0 +1,371 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xml
import (
"fmt"
"reflect"
"strings"
"sync"
)
// typeInfo holds details for the xml representation of a type.
type typeInfo struct {
xmlname *fieldInfo
fields []fieldInfo
}
// fieldInfo holds details for the xml representation of a single field.
type fieldInfo struct {
idx []int
name string
xmlns string
flags fieldFlags
parents []string
}
type fieldFlags int
const (
fElement fieldFlags = 1 << iota
fAttr
fCharData
fInnerXml
fComment
fAny
fOmitEmpty
fMode = fElement | fAttr | fCharData | fInnerXml | fComment | fAny
)
var tinfoMap = make(map[reflect.Type]*typeInfo)
var tinfoLock sync.RWMutex
var nameType = reflect.TypeOf(Name{})
// getTypeInfo returns the typeInfo structure with details necessary
// for marshalling and unmarshalling typ.
func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
tinfoLock.RLock()
tinfo, ok := tinfoMap[typ]
tinfoLock.RUnlock()
if ok {
return tinfo, nil
}
tinfo = &typeInfo{}
if typ.Kind() == reflect.Struct && typ != nameType {
n := typ.NumField()
for i := 0; i < n; i++ {
f := typ.Field(i)
if f.PkgPath != "" || f.Tag.Get("xml") == "-" {
continue // Private field
}
// For embedded structs, embed its fields.
if f.Anonymous {
t := f.Type
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() == reflect.Struct {
inner, err := getTypeInfo(t)
if err != nil {
return nil, err
}
if tinfo.xmlname == nil {
tinfo.xmlname = inner.xmlname
}
for _, finfo := range inner.fields {
finfo.idx = append([]int{i}, finfo.idx...)
if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
return nil, err
}
}
continue
}
}
finfo, err := structFieldInfo(typ, &f)
if err != nil {
return nil, err
}
if f.Name == "XMLName" {
tinfo.xmlname = finfo
continue
}
// Add the field if it doesn't conflict with other fields.
if err := addFieldInfo(typ, tinfo, finfo); err != nil {
return nil, err
}
}
}
tinfoLock.Lock()
tinfoMap[typ] = tinfo
tinfoLock.Unlock()
return tinfo, nil
}
// structFieldInfo builds and returns a fieldInfo for f.
func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) {
finfo := &fieldInfo{idx: f.Index}
// Split the tag from the xml namespace if necessary.
tag := f.Tag.Get("xml")
if i := strings.Index(tag, " "); i >= 0 {
finfo.xmlns, tag = tag[:i], tag[i+1:]
}
// Parse flags.
tokens := strings.Split(tag, ",")
if len(tokens) == 1 {
finfo.flags = fElement
} else {
tag = tokens[0]
for _, flag := range tokens[1:] {
switch flag {
case "attr":
finfo.flags |= fAttr
case "chardata":
finfo.flags |= fCharData
case "innerxml":
finfo.flags |= fInnerXml
case "comment":
finfo.flags |= fComment
case "any":
finfo.flags |= fAny
case "omitempty":
finfo.flags |= fOmitEmpty
}
}
// Validate the flags used.
valid := true
switch mode := finfo.flags & fMode; mode {
case 0:
finfo.flags |= fElement
case fAttr, fCharData, fInnerXml, fComment, fAny:
if f.Name == "XMLName" || tag != "" && mode != fAttr {
valid = false
}
default:
// This will also catch multiple modes in a single field.
valid = false
}
if finfo.flags&fMode == fAny {
finfo.flags |= fElement
}
if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
valid = false
}
if !valid {
return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q",
f.Name, typ, f.Tag.Get("xml"))
}
}
// Use of xmlns without a name is not allowed.
if finfo.xmlns != "" && tag == "" {
return nil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q",
f.Name, typ, f.Tag.Get("xml"))
}
if f.Name == "XMLName" {
// The XMLName field records the XML element name. Don't
// process it as usual because its name should default to
// empty rather than to the field name.
finfo.name = tag
return finfo, nil
}
if tag == "" {
// If the name part of the tag is completely empty, get
// default from XMLName of underlying struct if feasible,
// or field name otherwise.
if xmlname := lookupXMLName(f.Type); xmlname != nil {
finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name
} else {
finfo.name = f.Name
}
return finfo, nil
}
if finfo.xmlns == "" && finfo.flags&fAttr == 0 {
// If it's an element no namespace specified, get the default
// from the XMLName of enclosing struct if possible.
if xmlname := lookupXMLName(typ); xmlname != nil {
finfo.xmlns = xmlname.xmlns
}
}
// Prepare field name and parents.
parents := strings.Split(tag, ">")
if parents[0] == "" {
parents[0] = f.Name
}
if parents[len(parents)-1] == "" {
return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ)
}
finfo.name = parents[len(parents)-1]
if len(parents) > 1 {
if (finfo.flags & fElement) == 0 {
return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
}
finfo.parents = parents[:len(parents)-1]
}
// If the field type has an XMLName field, the names must match
// so that the behavior of both marshalling and unmarshalling
// is straightforward and unambiguous.
if finfo.flags&fElement != 0 {
ftyp := f.Type
xmlname := lookupXMLName(ftyp)
if xmlname != nil && xmlname.name != finfo.name {
return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName",
finfo.name, typ, f.Name, xmlname.name, ftyp)
}
}
return finfo, nil
}
// lookupXMLName returns the fieldInfo for typ's XMLName field
// in case it exists and has a valid xml field tag, otherwise
// it returns nil.
func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) {
for typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
if typ.Kind() != reflect.Struct {
return nil
}
for i, n := 0, typ.NumField(); i < n; i++ {
f := typ.Field(i)
if f.Name != "XMLName" {
continue
}
finfo, err := structFieldInfo(typ, &f)
if finfo.name != "" && err == nil {
return finfo
}
// Also consider errors as a non-existent field tag
// and let getTypeInfo itself report the error.
break
}
return nil
}
func min(a, b int) int {
if a <= b {
return a
}
return b
}
// addFieldInfo adds finfo to tinfo.fields if there are no
// conflicts, or if conflicts arise from previous fields that were
// obtained from deeper embedded structures than finfo. In the latter
// case, the conflicting entries are dropped.
// A conflict occurs when the path (parent + name) to a field is
// itself a prefix of another path, or when two paths match exactly.
// It is okay for field paths to share a common, shorter prefix.
func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error {
var conflicts []int
Loop:
// First, figure all conflicts. Most working code will have none.
for i := range tinfo.fields {
oldf := &tinfo.fields[i]
if oldf.flags&fMode != newf.flags&fMode {
continue
}
if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns {
continue
}
minl := min(len(newf.parents), len(oldf.parents))
for p := 0; p < minl; p++ {
if oldf.parents[p] != newf.parents[p] {
continue Loop
}
}
if len(oldf.parents) > len(newf.parents) {
if oldf.parents[len(newf.parents)] == newf.name {
conflicts = append(conflicts, i)
}
} else if len(oldf.parents) < len(newf.parents) {
if newf.parents[len(oldf.parents)] == oldf.name {
conflicts = append(conflicts, i)
}
} else {
if newf.name == oldf.name {
conflicts = append(conflicts, i)
}
}
}
// Without conflicts, add the new field and return.
if conflicts == nil {
tinfo.fields = append(tinfo.fields, *newf)
return nil
}
// If any conflict is shallower, ignore the new field.
// This matches the Go field resolution on embedding.
for _, i := range conflicts {
if len(tinfo.fields[i].idx) < len(newf.idx) {
return nil
}
}
// Otherwise, if any of them is at the same depth level, it's an error.
for _, i := range conflicts {
oldf := &tinfo.fields[i]
if len(oldf.idx) == len(newf.idx) {
f1 := typ.FieldByIndex(oldf.idx)
f2 := typ.FieldByIndex(newf.idx)
return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
}
}
// Otherwise, the new field is shallower, and thus takes precedence,
// so drop the conflicting fields from tinfo and append the new one.
for c := len(conflicts) - 1; c >= 0; c-- {
i := conflicts[c]
copy(tinfo.fields[i:], tinfo.fields[i+1:])
tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
}
tinfo.fields = append(tinfo.fields, *newf)
return nil
}
// A TagPathError represents an error in the unmarshalling process
// caused by the use of field tags with conflicting paths.
type TagPathError struct {
Struct reflect.Type
Field1, Tag1 string
Field2, Tag2 string
}
func (e *TagPathError) Error() string {
return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2)
}
// value returns v's field value corresponding to finfo.
// It's equivalent to v.FieldByIndex(finfo.idx), but initializes
// and dereferences pointers as necessary.
func (finfo *fieldInfo) value(v reflect.Value) reflect.Value {
for i, x := range finfo.idx {
if i > 0 {
t := v.Type()
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
v = v.Elem()
}
}
v = v.Field(x)
}
return v
}
File diff suppressed because it is too large Load Diff
+445
View File
@@ -0,0 +1,445 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package webdav
import (
"container/heap"
"errors"
"strconv"
"strings"
"sync"
"time"
)
var (
// ErrConfirmationFailed is returned by a LockSystem's Confirm method.
ErrConfirmationFailed = errors.New("webdav: confirmation failed")
// ErrForbidden is returned by a LockSystem's Unlock method.
ErrForbidden = errors.New("webdav: forbidden")
// ErrLocked is returned by a LockSystem's Create, Refresh and Unlock methods.
ErrLocked = errors.New("webdav: locked")
// ErrNoSuchLock is returned by a LockSystem's Refresh and Unlock methods.
ErrNoSuchLock = errors.New("webdav: no such lock")
)
// Condition can match a WebDAV resource, based on a token or ETag.
// Exactly one of Token and ETag should be non-empty.
type Condition struct {
Not bool
Token string
ETag string
}
// LockSystem manages access to a collection of named resources. The elements
// in a lock name are separated by slash ('/', U+002F) characters, regardless
// of host operating system convention.
type LockSystem interface {
// Confirm confirms that the caller can claim all of the locks specified by
// the given conditions, and that holding the union of all of those locks
// gives exclusive access to all of the named resources. Up to two resources
// can be named. Empty names are ignored.
//
// Exactly one of release and err will be non-nil. If release is non-nil,
// all of the requested locks are held until release is called. Calling
// release does not unlock the lock, in the WebDAV UNLOCK sense, but once
// Confirm has confirmed that a lock claim is valid, that lock cannot be
// Confirmed again until it has been released.
//
// If Confirm returns ErrConfirmationFailed then the Handler will continue
// to try any other set of locks presented (a WebDAV HTTP request can
// present more than one set of locks). If it returns any other non-nil
// error, the Handler will write a "500 Internal Server Error" HTTP status.
Confirm(now time.Time, name0, name1 string, conditions ...Condition) (release func(), err error)
// Create creates a lock with the given depth, duration, owner and root
// (name). The depth will either be negative (meaning infinite) or zero.
//
// If Create returns ErrLocked then the Handler will write a "423 Locked"
// HTTP status. If it returns any other non-nil error, the Handler will
// write a "500 Internal Server Error" HTTP status.
//
// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for
// when to use each error.
//
// The token returned identifies the created lock. It should be an absolute
// URI as defined by RFC 3986, Section 4.3. In particular, it should not
// contain whitespace.
Create(now time.Time, details LockDetails) (token string, err error)
// Refresh refreshes the lock with the given token.
//
// If Refresh returns ErrLocked then the Handler will write a "423 Locked"
// HTTP Status. If Refresh returns ErrNoSuchLock then the Handler will write
// a "412 Precondition Failed" HTTP Status. If it returns any other non-nil
// error, the Handler will write a "500 Internal Server Error" HTTP status.
//
// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for
// when to use each error.
Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error)
// Unlock unlocks the lock with the given token.
//
// If Unlock returns ErrForbidden then the Handler will write a "403
// Forbidden" HTTP Status. If Unlock returns ErrLocked then the Handler
// will write a "423 Locked" HTTP status. If Unlock returns ErrNoSuchLock
// then the Handler will write a "409 Conflict" HTTP Status. If it returns
// any other non-nil error, the Handler will write a "500 Internal Server
// Error" HTTP status.
//
// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.11.1 for
// when to use each error.
Unlock(now time.Time, token string) error
}
// LockDetails are a lock's metadata.
type LockDetails struct {
// Root is the root resource name being locked. For a zero-depth lock, the
// root is the only resource being locked.
Root string
// Duration is the lock timeout. A negative duration means infinite.
Duration time.Duration
// OwnerXML is the verbatim <owner> XML given in a LOCK HTTP request.
//
// TODO: does the "verbatim" nature play well with XML namespaces?
// Does the OwnerXML field need to have more structure? See
// https://codereview.appspot.com/175140043/#msg2
OwnerXML string
// ZeroDepth is whether the lock has zero depth. If it does not have zero
// depth, it has infinite depth.
ZeroDepth bool
}
// NewMemLS returns a new in-memory LockSystem.
func NewMemLS() LockSystem {
return &memLS{
byName: make(map[string]*memLSNode),
byToken: make(map[string]*memLSNode),
gen: uint64(time.Now().Unix()),
}
}
type memLS struct {
mu sync.Mutex
byName map[string]*memLSNode
byToken map[string]*memLSNode
gen uint64
// byExpiry only contains those nodes whose LockDetails have a finite
// Duration and are yet to expire.
byExpiry byExpiry
}
func (m *memLS) nextToken() string {
m.gen++
return strconv.FormatUint(m.gen, 10)
}
func (m *memLS) collectExpiredNodes(now time.Time) {
for len(m.byExpiry) > 0 {
if now.Before(m.byExpiry[0].expiry) {
break
}
m.remove(m.byExpiry[0])
}
}
func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condition) (func(), error) {
m.mu.Lock()
defer m.mu.Unlock()
m.collectExpiredNodes(now)
var n0, n1 *memLSNode
if name0 != "" {
if n0 = m.lookup(slashClean(name0), conditions...); n0 == nil {
return nil, ErrConfirmationFailed
}
}
if name1 != "" {
if n1 = m.lookup(slashClean(name1), conditions...); n1 == nil {
return nil, ErrConfirmationFailed
}
}
// Don't hold the same node twice.
if n1 == n0 {
n1 = nil
}
if n0 != nil {
m.hold(n0)
}
if n1 != nil {
m.hold(n1)
}
return func() {
m.mu.Lock()
defer m.mu.Unlock()
if n1 != nil {
m.unhold(n1)
}
if n0 != nil {
m.unhold(n0)
}
}, nil
}
// lookup returns the node n that locks the named resource, provided that n
// matches at least one of the given conditions and that lock isn't held by
// another party. Otherwise, it returns nil.
//
// n may be a parent of the named resource, if n is an infinite depth lock.
func (m *memLS) lookup(name string, conditions ...Condition) (n *memLSNode) {
// TODO: support Condition.Not and Condition.ETag.
for _, c := range conditions {
n = m.byToken[c.Token]
if n == nil || n.held {
continue
}
if name == n.details.Root {
return n
}
if n.details.ZeroDepth {
continue
}
if n.details.Root == "/" || strings.HasPrefix(name, n.details.Root+"/") {
return n
}
}
return nil
}
func (m *memLS) hold(n *memLSNode) {
if n.held {
panic("webdav: memLS inconsistent held state")
}
n.held = true
if n.details.Duration >= 0 && n.byExpiryIndex >= 0 {
heap.Remove(&m.byExpiry, n.byExpiryIndex)
}
}
func (m *memLS) unhold(n *memLSNode) {
if !n.held {
panic("webdav: memLS inconsistent held state")
}
n.held = false
if n.details.Duration >= 0 {
heap.Push(&m.byExpiry, n)
}
}
func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.collectExpiredNodes(now)
details.Root = slashClean(details.Root)
if !m.canCreate(details.Root, details.ZeroDepth) {
return "", ErrLocked
}
n := m.create(details.Root)
n.token = m.nextToken()
m.byToken[n.token] = n
n.details = details
if n.details.Duration >= 0 {
n.expiry = now.Add(n.details.Duration)
heap.Push(&m.byExpiry, n)
}
return n.token, nil
}
func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) {
m.mu.Lock()
defer m.mu.Unlock()
m.collectExpiredNodes(now)
n := m.byToken[token]
if n == nil {
return LockDetails{}, ErrNoSuchLock
}
if n.held {
return LockDetails{}, ErrLocked
}
if n.byExpiryIndex >= 0 {
heap.Remove(&m.byExpiry, n.byExpiryIndex)
}
n.details.Duration = duration
if n.details.Duration >= 0 {
n.expiry = now.Add(n.details.Duration)
heap.Push(&m.byExpiry, n)
}
return n.details, nil
}
func (m *memLS) Unlock(now time.Time, token string) error {
m.mu.Lock()
defer m.mu.Unlock()
m.collectExpiredNodes(now)
n := m.byToken[token]
if n == nil {
return ErrNoSuchLock
}
if n.held {
return ErrLocked
}
m.remove(n)
return nil
}
func (m *memLS) canCreate(name string, zeroDepth bool) bool {
return walkToRoot(name, func(name0 string, first bool) bool {
n := m.byName[name0]
if n == nil {
return true
}
if first {
if n.token != "" {
// The target node is already locked.
return false
}
if !zeroDepth {
// The requested lock depth is infinite, and the fact that n exists
// (n != nil) means that a descendent of the target node is locked.
return false
}
} else if n.token != "" && !n.details.ZeroDepth {
// An ancestor of the target node is locked with infinite depth.
return false
}
return true
})
}
func (m *memLS) create(name string) (ret *memLSNode) {
walkToRoot(name, func(name0 string, first bool) bool {
n := m.byName[name0]
if n == nil {
n = &memLSNode{
details: LockDetails{
Root: name0,
},
byExpiryIndex: -1,
}
m.byName[name0] = n
}
n.refCount++
if first {
ret = n
}
return true
})
return ret
}
func (m *memLS) remove(n *memLSNode) {
delete(m.byToken, n.token)
n.token = ""
walkToRoot(n.details.Root, func(name0 string, first bool) bool {
x := m.byName[name0]
x.refCount--
if x.refCount == 0 {
delete(m.byName, name0)
}
return true
})
if n.byExpiryIndex >= 0 {
heap.Remove(&m.byExpiry, n.byExpiryIndex)
}
}
func walkToRoot(name string, f func(name0 string, first bool) bool) bool {
for first := true; ; first = false {
if !f(name, first) {
return false
}
if name == "/" {
break
}
name = name[:strings.LastIndex(name, "/")]
if name == "" {
name = "/"
}
}
return true
}
type memLSNode struct {
// details are the lock metadata. Even if this node's name is not explicitly locked,
// details.Root will still equal the node's name.
details LockDetails
// token is the unique identifier for this node's lock. An empty token means that
// this node is not explicitly locked.
token string
// refCount is the number of self-or-descendent nodes that are explicitly locked.
refCount int
// expiry is when this node's lock expires.
expiry time.Time
// byExpiryIndex is the index of this node in memLS.byExpiry. It is -1
// if this node does not expire, or has expired.
byExpiryIndex int
// held is whether this node's lock is actively held by a Confirm call.
held bool
}
type byExpiry []*memLSNode
func (b *byExpiry) Len() int {
return len(*b)
}
func (b *byExpiry) Less(i, j int) bool {
return (*b)[i].expiry.Before((*b)[j].expiry)
}
func (b *byExpiry) Swap(i, j int) {
(*b)[i], (*b)[j] = (*b)[j], (*b)[i]
(*b)[i].byExpiryIndex = i
(*b)[j].byExpiryIndex = j
}
func (b *byExpiry) Push(x interface{}) {
n := x.(*memLSNode)
n.byExpiryIndex = len(*b)
*b = append(*b, n)
}
func (b *byExpiry) Pop() interface{} {
i := len(*b) - 1
n := (*b)[i]
(*b)[i] = nil
n.byExpiryIndex = -1
*b = (*b)[:i]
return n
}
const infiniteTimeout = -1
// parseTimeout parses the Timeout HTTP header, as per section 10.7. If s is
// empty, an infiniteTimeout is returned.
func parseTimeout(s string) (time.Duration, error) {
if s == "" {
return infiniteTimeout, nil
}
if i := strings.IndexByte(s, ','); i >= 0 {
s = s[:i]
}
s = strings.TrimSpace(s)
if s == "Infinite" {
return infiniteTimeout, nil
}
const pre = "Second-"
if !strings.HasPrefix(s, pre) {
return 0, errInvalidTimeout
}
s = s[len(pre):]
if s == "" || s[0] < '0' || '9' < s[0] {
return 0, errInvalidTimeout
}
n, err := strconv.ParseInt(s, 10, 64)
if err != nil || 1<<32-1 < n {
return 0, errInvalidTimeout
}
return time.Duration(n) * time.Second, nil
}
+413
View File
@@ -0,0 +1,413 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package webdav
import (
"bytes"
"context"
"encoding/xml"
"errors"
"fmt"
"net/http"
"strconv"
"time"
)
type FileInfo interface {
GetSize() uint64
GetName() string
ModTime() time.Time
IsDir() bool
//GetPosition() string
}
// Proppatch describes a property update instruction as defined in RFC 4918.
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH
type Proppatch struct {
// Remove specifies whether this patch removes properties. If it does not
// remove them, it sets them.
Remove bool
// Props contains the properties to be set or removed.
Props []Property
}
// Propstat describes a XML propstat element as defined in RFC 4918.
// See http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat
type Propstat struct {
// Props contains the properties for which Status applies.
Props []Property
// Status defines the HTTP status code of the properties in Prop.
// Allowed values include, but are not limited to the WebDAV status
// code extensions for HTTP/1.1.
// http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
Status int
// XMLError contains the XML representation of the optional error element.
// XML content within this field must not rely on any predefined
// namespace declarations or prefixes. If empty, the XML error element
// is omitted.
XMLError string
// ResponseDescription contains the contents of the optional
// responsedescription field. If empty, the XML element is omitted.
ResponseDescription string
}
// makePropstats returns a slice containing those of x and y whose Props slice
// is non-empty. If both are empty, it returns a slice containing an otherwise
// zero Propstat whose HTTP status code is 200 OK.
func makePropstats(x, y Propstat) []Propstat {
pstats := make([]Propstat, 0, 2)
if len(x.Props) != 0 {
pstats = append(pstats, x)
}
if len(y.Props) != 0 {
pstats = append(pstats, y)
}
if len(pstats) == 0 {
pstats = append(pstats, Propstat{
Status: http.StatusOK,
})
}
return pstats
}
// DeadPropsHolder holds the dead properties of a resource.
//
// Dead properties are those properties that are explicitly defined. In
// comparison, live properties, such as DAV:getcontentlength, are implicitly
// defined by the underlying resource, and cannot be explicitly overridden or
// removed. See the Terminology section of
// http://www.webdav.org/specs/rfc4918.html#rfc.section.3
//
// There is a whitelist of the names of live properties. This package handles
// all live properties, and will only pass non-whitelisted names to the Patch
// method of DeadPropsHolder implementations.
type DeadPropsHolder interface {
// DeadProps returns a copy of the dead properties held.
DeadProps() (map[xml.Name]Property, error)
// Patch patches the dead properties held.
//
// Patching is atomic; either all or no patches succeed. It returns (nil,
// non-nil) if an internal server error occurred, otherwise the Propstats
// collectively contain one Property for each proposed patch Property. If
// all patches succeed, Patch returns a slice of length one and a Propstat
// element with a 200 OK HTTP status code. If none succeed, for reasons
// other than an internal server error, no Propstat has status 200 OK.
//
// For more details on when various HTTP status codes apply, see
// http://www.webdav.org/specs/rfc4918.html#PROPPATCH-status
Patch([]Proppatch) ([]Propstat, error)
}
// liveProps contains all supported, protected DAV: properties.
var liveProps = map[xml.Name]struct {
// findFn implements the propfind function of this property. If nil,
// it indicates a hidden property.
findFn func(context.Context, *FileSystem, LockSystem, string, FileInfo) (string, error)
// dir is true if the property applies to directories.
dir bool
}{
{Space: "DAV:", Local: "resourcetype"}: {
findFn: findResourceType,
dir: true,
},
{Space: "DAV:", Local: "displayname"}: {
findFn: findDisplayName,
dir: true,
},
{Space: "DAV:", Local: "getcontentlength"}: {
findFn: findContentLength,
dir: false,
},
{Space: "DAV:", Local: "getlastmodified"}: {
findFn: findLastModified,
// http://webdav.org/specs/rfc4918.html#PROPERTY_getlastmodified
// suggests that getlastmodified should only apply to GETable
// resources, and this package does not support GET on directories.
//
// Nonetheless, some WebDAV clients expect child directories to be
// sortable by getlastmodified date, so this value is true, not false.
// See golang.org/issue/15334.
dir: true,
},
{Space: "DAV:", Local: "creationdate"}: {
findFn: nil,
dir: false,
},
{Space: "DAV:", Local: "getcontentlanguage"}: {
findFn: nil,
dir: false,
},
{Space: "DAV:", Local: "getcontenttype"}: {
findFn: findContentType,
dir: false,
},
{Space: "DAV:", Local: "getetag"}: {
findFn: findETag,
// findETag implements ETag as the concatenated hex values of a file's
// modification time and size. This is not a reliable synchronization
// mechanism for directories, so we do not advertise getetag for DAV
// collections.
dir: false,
},
// TODO: The lockdiscovery property requires LockSystem to list the
// active locks on a resource.
{Space: "DAV:", Local: "lockdiscovery"}: {},
{Space: "DAV:", Local: "supportedlock"}: {
findFn: findSupportedLock,
dir: true,
},
}
// TODO(nigeltao) merge props and allprop?
// Props returns the status of the properties named pnames for resource name.
//
// Each Propstat has a unique status and each property name will only be part
// of one Propstat element.
func props(ctx context.Context, fs *FileSystem, ls LockSystem, fi FileInfo, pnames []xml.Name) ([]Propstat, error) {
isDir := fi.IsDir()
var deadProps map[xml.Name]Property
pstatOK := Propstat{Status: http.StatusOK}
pstatNotFound := Propstat{Status: http.StatusNotFound}
for _, pn := range pnames {
// If this file has dead properties, check if they contain pn.
if dp, ok := deadProps[pn]; ok {
pstatOK.Props = append(pstatOK.Props, dp)
continue
}
// Otherwise, it must either be a live property or we don't know it.
if prop := liveProps[pn]; prop.findFn != nil && (prop.dir || !isDir) {
innerXML, err := prop.findFn(ctx, fs, ls, fi.GetName(), fi)
if err != nil {
return nil, err
}
pstatOK.Props = append(pstatOK.Props, Property{
XMLName: pn,
InnerXML: []byte(innerXML),
})
} else {
pstatNotFound.Props = append(pstatNotFound.Props, Property{
XMLName: pn,
})
}
}
return makePropstats(pstatOK, pstatNotFound), nil
}
// Propnames returns the property names defined for resource name.
func propnames(ctx context.Context, fs *FileSystem, ls LockSystem, fi FileInfo) ([]xml.Name, error) {
isDir := fi.IsDir()
var deadProps map[xml.Name]Property
pnames := make([]xml.Name, 0, len(liveProps)+len(deadProps))
for pn, prop := range liveProps {
if prop.findFn != nil && (prop.dir || !isDir) {
pnames = append(pnames, pn)
}
}
return pnames, nil
}
// Allprop returns the properties defined for resource name and the properties
// named in include.
//
// Note that RFC 4918 defines 'allprop' to return the DAV: properties defined
// within the RFC plus dead properties. Other live properties should only be
// returned if they are named in 'include'.
//
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
func allprop(ctx context.Context, fs *FileSystem, ls LockSystem, info FileInfo, include []xml.Name) ([]Propstat, error) {
pnames, err := propnames(ctx, fs, ls, info)
if err != nil {
return nil, err
}
// Add names from include if they are not already covered in pnames.
nameset := make(map[xml.Name]bool)
for _, pn := range pnames {
nameset[pn] = true
}
for _, pn := range include {
if !nameset[pn] {
pnames = append(pnames, pn)
}
}
return props(ctx, fs, ls, info, pnames)
}
// Patch patches the properties of resource name. The return values are
// constrained in the same manner as DeadPropsHolder.Patch.
func patch(ctx context.Context, fs *FileSystem, ls LockSystem, name string, patches []Proppatch) ([]Propstat, error) {
conflict := false
loop:
for _, patch := range patches {
for _, p := range patch.Props {
if _, ok := liveProps[p.XMLName]; ok {
conflict = true
break loop
}
}
}
if conflict {
pstatForbidden := Propstat{
Status: http.StatusForbidden,
XMLError: `<D:cannot-modify-protected-property xmlns:D="DAV:"/>`,
}
pstatFailedDep := Propstat{
Status: StatusFailedDependency,
}
for _, patch := range patches {
for _, p := range patch.Props {
if _, ok := liveProps[p.XMLName]; ok {
pstatForbidden.Props = append(pstatForbidden.Props, Property{XMLName: p.XMLName})
} else {
pstatFailedDep.Props = append(pstatFailedDep.Props, Property{XMLName: p.XMLName})
}
}
}
return makePropstats(pstatForbidden, pstatFailedDep), nil
}
// The file doesn't implement the optional DeadPropsHolder interface, so
// all patches are forbidden.
pstat := Propstat{Status: http.StatusOK}
for _, patch := range patches {
for _, p := range patch.Props {
pstat.Props = append(pstat.Props, Property{XMLName: p.XMLName})
}
}
return []Propstat{pstat}, nil
}
func escapeXML(s string) string {
for i := 0; i < len(s); i++ {
// As an optimization, if s contains only ASCII letters, digits or a
// few special characters, the escaped value is s itself and we don't
// need to allocate a buffer and convert between string and []byte.
switch c := s[i]; {
case c == ' ' || c == '_' ||
('+' <= c && c <= '9') || // Digits as well as + , - . and /
('A' <= c && c <= 'Z') ||
('a' <= c && c <= 'z'):
continue
}
// Otherwise, go through the full escaping process.
var buf bytes.Buffer
xml.EscapeText(&buf, []byte(s))
return buf.String()
}
return s
}
func findResourceType(ctx context.Context, fs *FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
if fi.IsDir() {
return `<D:collection xmlns:D="DAV:"/>`, nil
}
return "", nil
}
func findDisplayName(ctx context.Context, fs *FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
if slashClean(name) == "/" {
// Hide the real name of a possibly prefixed root directory.
return "", nil
}
return escapeXML(fi.GetName()), nil
}
func findContentLength(ctx context.Context, fs *FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
return strconv.FormatUint(fi.GetSize(), 10), nil
}
func findLastModified(ctx context.Context, fs *FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
return fi.ModTime().UTC().Format(http.TimeFormat), nil
}
// ErrNotImplemented should be returned by optional interfaces if they
// want the original implementation to be used.
var ErrNotImplemented = errors.New("not implemented")
// ContentTyper is an optional interface for the os.FileInfo
// objects returned by the FileSystem.
//
// If this interface is defined then it will be used to read the
// content type from the object.
//
// If this interface is not defined the file will be opened and the
// content type will be guessed from the initial contents of the file.
type ContentTyper interface {
// ContentType returns the content type for the file.
//
// If this returns error ErrNotImplemented then the error will
// be ignored and the base implementation will be used
// instead.
ContentType(ctx context.Context) (string, error)
}
func findContentType(ctx context.Context, fs *FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
//if do, ok := fi.(ContentTyper); ok {
// ctype, err := do.ContentType(ctx)
// if err != ErrNotImplemented {
// return ctype, err
// }
//}
//f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
//if err != nil {
// return "", err
//}
//defer f.Close()
//// This implementation is based on serveContent's code in the standard net/http package.
//ctype := mime.TypeByExtension(filepath.Ext(name))
//if ctype != "" {
// return ctype, nil
//}
//// Read a chunk to decide between utf-8 text and binary.
//var buf [512]byte
//n, err := io.ReadFull(f, buf[:])
//if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
// return "", err
//}
//ctype = http.DetectContentType(buf[:n])
//// Rewind file.
//_, err = f.Seek(0, os.SEEK_SET)
//return ctype, err
return "", nil
}
// ETager is an optional interface for the os.FileInfo objects
// returned by the FileSystem.
//
// If this interface is defined then it will be used to read the ETag
// for the object.
//
// If this interface is not defined an ETag will be computed using the
// ModTime() and the Size() methods of the os.FileInfo object.
type ETager interface {
// ETag returns an ETag for the file. This should be of the
// form "value" or W/"value"
//
// If this returns error ErrNotImplemented then the error will
// be ignored and the base implementation will be used
// instead.
ETag(ctx context.Context) (string, error)
}
func findETag(ctx context.Context, fs *FileSystem, ls LockSystem, reqPath string, fi FileInfo) (string, error) {
return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.GetSize()), nil
}
func findSupportedLock(ctx context.Context, fs *FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
return `` +
`<D:lockentry xmlns:D="DAV:">` +
`<D:lockscope><D:exclusive/></D:lockscope>` +
`<D:locktype><D:write/></D:locktype>` +
`</D:lockentry>`, nil
}
+731
View File
@@ -0,0 +1,731 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package webdav provides a WebDAV server implementation.
package webdav // import "golang.org/x/net/webdav"
import (
"context"
"errors"
"fmt"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"net/http"
"net/url"
"path"
"strings"
"time"
)
type Handler struct {
// Prefix is the URL path prefix to strip from WebDAV resource paths.
Prefix string
// LockSystem is the lock management system.
LockSystem LockSystem
// Logger is an optional error logger. If non-nil, it will be called
// for all HTTP requests.
Logger func(*http.Request, error)
}
func (h *Handler) stripPrefix(p string) (string, int, error) {
if h.Prefix == "" {
return p, http.StatusOK, nil
}
prefix := h.Prefix
if r := strings.TrimPrefix(p, prefix); len(r) < len(p) {
if len(r) == 0 {
r = "/"
}
return utils.RemoveLastSlash(r), http.StatusOK, nil
}
return p, http.StatusNotFound, errPrefixMismatch
}
// isPathExist 路径是否存在
func isPathExist(ctx context.Context, fs *FileSystem, path string) (bool, FileInfo) {
file, err := fs.File(path)
if err != nil {
return false, nil
}
return true, file
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, fs *FileSystem) {
status, err := http.StatusBadRequest, errUnsupportedMethod
if h.LockSystem == nil {
status, err = http.StatusInternalServerError, errNoLockSystem
} else {
switch r.Method {
case "OPTIONS":
status, err = h.handleOptions(w, r, fs)
case "GET", "HEAD", "POST":
status, err = h.handleGetHeadPost(w, r, fs)
case "DELETE":
status, err = h.handleDelete(w, r, fs)
case "PUT":
status, err = h.handlePut(w, r, fs)
case "MKCOL":
status, err = h.handleMkcol(w, r, fs)
case "COPY", "MOVE":
status, err = h.handleCopyMove(w, r, fs)
case "LOCK":
status, err = h.handleLock(w, r, fs)
case "UNLOCK":
status, err = h.handleUnlock(w, r, fs)
case "PROPFIND":
status, err = h.handlePropfind(w, r, fs)
case "PROPPATCH":
status, err = h.handleProppatch(w, r, fs)
}
}
if err != nil {
log.Error(err)
}
if status != 0 {
w.WriteHeader(status)
if status != http.StatusNoContent {
w.Write([]byte(StatusText(status)))
}
}
if h.Logger != nil {
h.Logger(r, err)
}
}
// OK
func (h *Handler) lock(now time.Time, root string, fs *FileSystem) (token string, status int, err error) {
token, err = h.LockSystem.Create(now, LockDetails{
Root: root,
Duration: infiniteTimeout,
ZeroDepth: true,
})
if err != nil {
if err == ErrLocked {
return "", StatusLocked, err
}
return "", http.StatusInternalServerError, err
}
return token, 0, nil
}
// ok
func (h *Handler) confirmLocks(r *http.Request, src, dst string, fs *FileSystem) (release func(), status int, err error) {
hdr := r.Header.Get("If")
if hdr == "" {
// An empty If header means that the client hasn't previously created locks.
// Even if this client doesn't care about locks, we still need to check that
// the resources aren't locked by another client, so we create temporary
// locks that would conflict with another client's locks. These temporary
// locks are unlocked at the end of the HTTP request.
now, srcToken, dstToken := time.Now(), "", ""
if src != "" {
srcToken, status, err = h.lock(now, src, fs)
if err != nil {
return nil, status, err
}
}
if dst != "" {
dstToken, status, err = h.lock(now, dst, fs)
if err != nil {
if srcToken != "" {
h.LockSystem.Unlock(now, srcToken)
}
return nil, status, err
}
}
return func() {
if dstToken != "" {
h.LockSystem.Unlock(now, dstToken)
}
if srcToken != "" {
h.LockSystem.Unlock(now, srcToken)
}
}, 0, nil
}
ih, ok := parseIfHeader(hdr)
if !ok {
return nil, http.StatusBadRequest, errInvalidIfHeader
}
// ih is a disjunction (OR) of ifLists, so any ifList will do.
for _, l := range ih.lists {
lsrc := l.resourceTag
if lsrc == "" {
lsrc = src
} else {
u, err := url.Parse(lsrc)
if err != nil {
continue
}
//if u.Host != r.Host {
// continue
//}
lsrc, status, err = h.stripPrefix(u.Path)
if err != nil {
return nil, status, err
}
}
release, err = h.LockSystem.Confirm(
time.Now(),
lsrc,
dst,
l.conditions...,
)
if err == ErrConfirmationFailed {
continue
}
if err != nil {
return nil, http.StatusInternalServerError, err
}
return release, 0, nil
}
// Section 10.4.1 says that "If this header is evaluated and all state lists
// fail, then the request must fail with a 412 (Precondition Failed) status."
// We follow the spec even though the cond_put_corrupt_token test case from
// the litmus test warns on seeing a 412 instead of a 423 (Locked).
return nil, http.StatusPreconditionFailed, ErrLocked
}
//OK
func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request, fs *FileSystem) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
if err != nil {
return status, err
}
ctx := r.Context()
allow := "OPTIONS, LOCK, PUT, MKCOL"
if exist, fi := isPathExist(ctx, fs, reqPath); exist {
log.Debugf("fi: %+v", fi)
if fi.IsDir() {
allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
} else {
allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT"
}
}
w.Header().Set("Allow", allow)
// http://www.webdav.org/specs/rfc4918.html#dav.compliance.classes
w.Header().Set("DAV", "1, 2")
// http://msdn.microsoft.com/en-au/library/cc250217.aspx
w.Header().Set("MS-Author-Via", "DAV")
return 0, nil
}
// OK
func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *FileSystem) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
if err != nil {
return status, err
}
ctx := r.Context()
exist, file := isPathExist(ctx, fs, reqPath)
if !exist {
return http.StatusNotFound, nil
}
etag, err := findETag(ctx, fs, h.LockSystem, reqPath, file)
if err != nil {
return http.StatusInternalServerError, err
}
w.Header().Set("ETag", etag)
log.Debugf("url: %+v", r.URL)
host := r.Host
link, err := fs.Link(host, reqPath)
if err != nil {
return http.StatusInternalServerError, err
}
http.Redirect(w, r, link, 302)
return 0, nil
}
// OK
func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request, fs *FileSystem) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
if err != nil {
return status, err
}
release, status, err := h.confirmLocks(r, reqPath, "", fs)
if err != nil {
return status, err
}
defer release()
//ctx := r.Context()
//// 尝试作为文件删除
//if ok, file := fs.IsFileExist(reqPath); ok {
// if err := fs.Delete(ctx, []uint{}, []uint{file.ID}, false); err != nil {
// return http.StatusMethodNotAllowed, err
// }
// return http.StatusNoContent, nil
//}
//
//// 尝试作为目录删除
//if ok, folder := fs.IsPathExist(reqPath); ok {
// if err := fs.Delete(ctx, []uint{folder.ID}, []uint{}, false); err != nil {
// return http.StatusMethodNotAllowed, err
// }
// return http.StatusNoContent, nil
//}
return http.StatusNotFound, nil
}
// OK
func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *FileSystem) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
if err != nil {
return status, err
}
release, status, err := h.confirmLocks(r, reqPath, "", fs)
if err != nil {
return status, err
}
defer release()
// TODO(rost): Support the If-Match, If-None-Match headers? See bradfitz'
// comments in http.checkEtag.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
etag, err := findETag(ctx, fs, h.LockSystem, reqPath, nil)
if err != nil {
return http.StatusInternalServerError, err
}
w.Header().Set("ETag", etag)
return http.StatusCreated, nil
}
// OK
func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request, fs *FileSystem) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
if err != nil {
return status, err
}
release, status, err := h.confirmLocks(r, reqPath, "", fs)
if err != nil {
return status, err
}
defer release()
ctx := r.Context()
if r.ContentLength > 0 {
return http.StatusUnsupportedMediaType, nil
}
if strings.Contains(r.UserAgent(), "rclone") {
//if _, ok := ctx.Value(fsctx.IgnoreDirectoryConflictCtx).(bool); !ok {
// ctx = context.WithValue(ctx, fsctx.IgnoreDirectoryConflictCtx, true)
//}
}
if _, err := fs.CreateDirectory(ctx, reqPath); err != nil {
return http.StatusConflict, err
}
return http.StatusCreated, nil
}
// OK
func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *FileSystem) (status int, err error) {
hdr := r.Header.Get("Destination")
if hdr == "" {
return http.StatusBadRequest, errInvalidDestination
}
u, err := url.Parse(hdr)
if err != nil {
return http.StatusBadRequest, errInvalidDestination
}
//if u.Host != "" && u.Host != r.Host {
// return http.StatusBadGateway, errInvalidDestination
//}
src, status, err := h.stripPrefix(r.URL.Path)
if err != nil {
return status, err
}
dst, status, err := h.stripPrefix(u.Path)
if err != nil {
return status, err
}
if dst == "" {
return http.StatusBadGateway, errInvalidDestination
}
if dst == src {
return http.StatusForbidden, errDestinationEqualsSource
}
ctx := r.Context()
isExist, target := isPathExist(ctx, fs, src)
if !isExist {
return http.StatusNotFound, nil
}
if r.Method == "COPY" {
// Section 7.5.1 says that a COPY only needs to lock the destination,
// not both destination and source. Strictly speaking, this is racy,
// even though a COPY doesn't modify the source, if a concurrent
// operation modifies the source. However, the litmus test explicitly
// checks that COPYing a locked-by-another source is OK.
release, status, err := h.confirmLocks(r, "", dst, fs)
if err != nil {
return status, err
}
defer release()
// Section 9.8.3 says that "The COPY method on a collection without a Depth
// header must act as if a Depth header with value "infinity" was included".
depth := infiniteDepth
if hdr := r.Header.Get("Depth"); hdr != "" {
depth = parseDepth(hdr)
if depth != 0 && depth != infiniteDepth {
// Section 9.8.3 says that "A client may submit a Depth header on a
// COPY on a collection with a value of "0" or "infinity"."
return http.StatusBadRequest, errInvalidDepth
}
}
return copyFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") != "F", depth, 0)
}
// windows下,某些情况下(网盘根目录下)Office保存文件时附带的锁token只包含源文件,
// 此处暂时去除了对dst锁的检查
release, status, err := h.confirmLocks(r, src, "", fs)
if err != nil {
return status, err
}
defer release()
// Section 9.9.2 says that "The MOVE method on a collection must act as if
// a "Depth: infinity" header was used on it. A client must not submit a
// Depth header on a MOVE on a collection with any value but "infinity"."
if hdr := r.Header.Get("Depth"); hdr != "" {
if parseDepth(hdr) != infiniteDepth {
return http.StatusBadRequest, errInvalidDepth
}
}
return moveFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") == "T")
}
// OK
func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *FileSystem) (retStatus int, retErr error) {
duration, err := parseTimeout(r.Header.Get("Timeout"))
if err != nil {
return http.StatusBadRequest, err
}
li, status, err := readLockInfo(r.Body)
if err != nil {
return status, err
}
//ctx := r.Context()
token, ld, now, created := "", LockDetails{}, time.Now(), false
if li == (lockInfo{}) {
// An empty lockInfo means to refresh the lock.
ih, ok := parseIfHeader(r.Header.Get("If"))
if !ok {
return http.StatusBadRequest, errInvalidIfHeader
}
if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 {
token = ih.lists[0].conditions[0].Token
}
if token == "" {
return http.StatusBadRequest, errInvalidLockToken
}
ld, err = h.LockSystem.Refresh(now, token, duration)
if err != nil {
if err == ErrNoSuchLock {
return http.StatusPreconditionFailed, err
}
return http.StatusInternalServerError, err
}
} else {
// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
// then the request MUST act as if a "Depth:infinity" had been submitted."
depth := infiniteDepth
if hdr := r.Header.Get("Depth"); hdr != "" {
depth = parseDepth(hdr)
if depth != 0 && depth != infiniteDepth {
// Section 9.10.3 says that "Values other than 0 or infinity must not be
// used with the Depth header on a LOCK method".
return http.StatusBadRequest, errInvalidDepth
}
}
reqPath, status, err := h.stripPrefix(r.URL.Path)
if err != nil {
return status, err
}
ld = LockDetails{
Root: reqPath,
Duration: duration,
OwnerXML: li.Owner.InnerXML,
ZeroDepth: depth == 0,
}
token, err = h.LockSystem.Create(now, ld)
if err != nil {
if err == ErrLocked {
return StatusLocked, err
}
return http.StatusInternalServerError, err
}
defer func() {
if retErr != nil {
h.LockSystem.Unlock(now, token)
}
}()
// Create the resource if it didn't previously exist.
//if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
// f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
// if err != nil {
// // TODO: detect missing intermediate dirs and return http.StatusConflict?
// return http.StatusInternalServerError, err
// }
// f.Close()
// created = true
//}
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
// Lock-Token value is a Coded-URL. We add angle brackets.
w.Header().Set("Lock-Token", "<"+token+">")
}
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
if created {
// This is "w.WriteHeader(http.StatusCreated)" and not "return
// http.StatusCreated, nil" because we write our own (XML) response to w
// and Handler.ServeHTTP would otherwise write "Created".
w.WriteHeader(http.StatusCreated)
}
writeLockInfo(w, token, ld)
return 0, nil
}
// OK
func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request, fs *FileSystem) (status int, err error) {
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
// Lock-Token value is a Coded-URL. We strip its angle brackets.
t := r.Header.Get("Lock-Token")
if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' {
return http.StatusBadRequest, errInvalidLockToken
}
t = t[1 : len(t)-1]
switch err = h.LockSystem.Unlock(time.Now(), t); err {
case nil:
return http.StatusNoContent, err
case ErrForbidden:
return http.StatusForbidden, err
case ErrLocked:
return StatusLocked, err
case ErrNoSuchLock:
return http.StatusConflict, err
default:
return http.StatusInternalServerError, err
}
}
// OK
func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *FileSystem) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
if err != nil {
return status, err
}
ctx := r.Context()
ok, fi := isPathExist(ctx, fs, reqPath)
if !ok {
return http.StatusNotFound, err
}
depth := infiniteDepth
if hdr := r.Header.Get("Depth"); hdr != "" {
depth = parseDepth(hdr)
if depth == invalidDepth {
return http.StatusBadRequest, errInvalidDepth
}
}
pf, status, err := readPropfind(r.Body)
if err != nil {
return status, err
}
mw := multistatusWriter{w: w}
walkFn := func(reqPath string, info FileInfo, err error) error {
if err != nil {
return err
}
var pstats []Propstat
if pf.Propname != nil {
pnames, err := propnames(ctx, fs, h.LockSystem, info)
if err != nil {
return err
}
pstat := Propstat{Status: http.StatusOK}
for _, xmlname := range pnames {
pstat.Props = append(pstat.Props, Property{XMLName: xmlname})
}
pstats = append(pstats, pstat)
} else if pf.Allprop != nil {
pstats, err = allprop(ctx, fs, h.LockSystem, info, pf.Prop)
} else {
pstats, err = props(ctx, fs, h.LockSystem, info, pf.Prop)
}
if err != nil {
return err
}
href := path.Join(h.Prefix, reqPath)
if href != "/" && info.IsDir() {
href += "/"
}
return mw.write(makePropstatResponse(href, pstats))
}
walkErr := walkFS(ctx, fs, depth, reqPath, fi, walkFn)
closeErr := mw.close()
if walkErr != nil {
return http.StatusInternalServerError, walkErr
}
if closeErr != nil {
return http.StatusInternalServerError, closeErr
}
return 0, nil
}
func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request, fs *FileSystem) (status int, err error) {
reqPath, status, err := h.stripPrefix(r.URL.Path)
if err != nil {
return status, err
}
release, status, err := h.confirmLocks(r, reqPath, "", fs)
if err != nil {
return status, err
}
defer release()
ctx := r.Context()
if exist, _ := isPathExist(ctx, fs, reqPath); !exist {
return http.StatusNotFound, nil
}
patches, status, err := readProppatch(r.Body)
if err != nil {
return status, err
}
pstats, err := patch(ctx, fs, h.LockSystem, reqPath, patches)
if err != nil {
return http.StatusInternalServerError, err
}
mw := multistatusWriter{w: w}
writeErr := mw.write(makePropstatResponse(r.URL.Path, pstats))
closeErr := mw.close()
if writeErr != nil {
return http.StatusInternalServerError, writeErr
}
if closeErr != nil {
return http.StatusInternalServerError, closeErr
}
return 0, nil
}
func makePropstatResponse(href string, pstats []Propstat) *response {
resp := response{
Href: []string{(&url.URL{Path: href}).EscapedPath()},
Propstat: make([]propstat, 0, len(pstats)),
}
for _, p := range pstats {
var xmlErr *xmlError
if p.XMLError != "" {
xmlErr = &xmlError{InnerXML: []byte(p.XMLError)}
}
resp.Propstat = append(resp.Propstat, propstat{
Status: fmt.Sprintf("HTTP/1.1 %d %s", p.Status, StatusText(p.Status)),
Prop: p.Props,
ResponseDescription: p.ResponseDescription,
Error: xmlErr,
})
}
return &resp
}
const (
infiniteDepth = -1
invalidDepth = -2
)
// parseDepth maps the strings "0", "1" and "infinity" to 0, 1 and
// infiniteDepth. Parsing any other string returns invalidDepth.
//
// Different WebDAV methods have further constraints on valid depths:
// - PROPFIND has no further restrictions, as per section 9.1.
// - COPY accepts only "0" or "infinity", as per section 9.8.3.
// - MOVE accepts only "infinity", as per section 9.9.2.
// - LOCK accepts only "0" or "infinity", as per section 9.10.3.
// These constraints are enforced by the handleXxx methods.
func parseDepth(s string) int {
switch s {
case "0":
return 0
case "1":
return 1
case "infinity":
return infiniteDepth
}
return invalidDepth
}
// http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
const (
StatusMulti = 207
StatusUnprocessableEntity = 422
StatusLocked = 423
StatusFailedDependency = 424
StatusInsufficientStorage = 507
)
func StatusText(code int) string {
switch code {
case StatusMulti:
return "Multi-Status"
case StatusUnprocessableEntity:
return "Unprocessable Entity"
case StatusLocked:
return "Locked"
case StatusFailedDependency:
return "Failed Dependency"
case StatusInsufficientStorage:
return "Insufficient Storage"
}
return http.StatusText(code)
}
var (
errDestinationEqualsSource = errors.New("webdav: destination equals source")
errDirectoryNotEmpty = errors.New("webdav: directory not empty")
errInvalidDepth = errors.New("webdav: invalid depth")
errInvalidDestination = errors.New("webdav: invalid destination")
errInvalidIfHeader = errors.New("webdav: invalid If header")
errInvalidLockInfo = errors.New("webdav: invalid lock info")
errInvalidLockToken = errors.New("webdav: invalid lock token")
errInvalidPropfind = errors.New("webdav: invalid propfind")
errInvalidProppatch = errors.New("webdav: invalid proppatch")
errInvalidResponse = errors.New("webdav: invalid response")
errInvalidTimeout = errors.New("webdav: invalid timeout")
errNoFileSystem = errors.New("webdav: no file system")
errNoLockSystem = errors.New("webdav: no lock system")
errNotADirectory = errors.New("webdav: not a directory")
errPrefixMismatch = errors.New("webdav: prefix mismatch")
errRecursionTooDeep = errors.New("webdav: recursion too deep")
errUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
errUnsupportedMethod = errors.New("webdav: unsupported method")
)
+519
View File
@@ -0,0 +1,519 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package webdav
// The XML encoding is covered by Section 14.
// http://www.webdav.org/specs/rfc4918.html#xml.element.definitions
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"net/http"
"time"
// As of https://go-review.googlesource.com/#/c/12772/ which was submitted
// in July 2015, this package uses an internal fork of the standard
// library's encoding/xml package, due to changes in the way namespaces
// were encoded. Such changes were introduced in the Go 1.5 cycle, but were
// rolled back in response to https://github.com/golang/go/issues/11841
//
// However, this package's exported API, specifically the Property and
// DeadPropsHolder types, need to refer to the standard library's version
// of the xml.Name type, as code that imports this package cannot refer to
// the internal version.
//
// This file therefore imports both the internal and external versions, as
// ixml and xml, and converts between them.
//
// In the long term, this package should use the standard library's version
// only, and the internal fork deleted, once
// https://github.com/golang/go/issues/13400 is resolved.
ixml "github.com/Xhofe/alist/server/webdav/internal/xml"
)
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo
type lockInfo struct {
XMLName ixml.Name `xml:"lockinfo"`
Exclusive *struct{} `xml:"lockscope>exclusive"`
Shared *struct{} `xml:"lockscope>shared"`
Write *struct{} `xml:"locktype>write"`
Owner owner `xml:"owner"`
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner
type owner struct {
InnerXML string `xml:",innerxml"`
}
func readLockInfo(r io.Reader) (li lockInfo, status int, err error) {
c := &countingReader{r: r}
if err = ixml.NewDecoder(c).Decode(&li); err != nil {
if err == io.EOF {
if c.n == 0 {
// An empty body means to refresh the lock.
// http://www.webdav.org/specs/rfc4918.html#refreshing-locks
return lockInfo{}, 0, nil
}
err = errInvalidLockInfo
}
return lockInfo{}, http.StatusBadRequest, err
}
// We only support exclusive (non-shared) write locks. In practice, these are
// the only types of locks that seem to matter.
if li.Exclusive == nil || li.Shared != nil || li.Write == nil {
return lockInfo{}, http.StatusNotImplemented, errUnsupportedLockInfo
}
return li, 0, nil
}
type countingReader struct {
n int
r io.Reader
}
func (c *countingReader) Read(p []byte) (int, error) {
n, err := c.r.Read(p)
c.n += n
return n, err
}
func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) {
depth := "infinity"
if ld.ZeroDepth {
depth = "0"
}
timeout := ld.Duration / time.Second
return fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
"<D:prop xmlns:D=\"DAV:\"><D:lockdiscovery><D:activelock>\n"+
" <D:locktype><D:write/></D:locktype>\n"+
" <D:lockscope><D:exclusive/></D:lockscope>\n"+
" <D:depth>%s</D:depth>\n"+
" <D:owner>%s</D:owner>\n"+
" <D:timeout>Second-%d</D:timeout>\n"+
" <D:locktoken><D:href>%s</D:href></D:locktoken>\n"+
" <D:lockroot><D:href>%s</D:href></D:lockroot>\n"+
"</D:activelock></D:lockdiscovery></D:prop>",
depth, ld.OwnerXML, timeout, escape(token), escape(ld.Root),
)
}
func escape(s string) string {
for i := 0; i < len(s); i++ {
switch s[i] {
case '"', '&', '\'', '<', '>':
b := bytes.NewBuffer(nil)
ixml.EscapeText(b, []byte(s))
return b.String()
}
}
return s
}
// Next returns the next token, if any, in the XML stream of d.
// RFC 4918 requires to ignore comments, processing instructions
// and directives.
// http://www.webdav.org/specs/rfc4918.html#property_values
// http://www.webdav.org/specs/rfc4918.html#xml-extensibility
func next(d *ixml.Decoder) (ixml.Token, error) {
for {
t, err := d.Token()
if err != nil {
return t, err
}
switch t.(type) {
case ixml.Comment, ixml.Directive, ixml.ProcInst:
continue
default:
return t, nil
}
}
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind)
type propfindProps []xml.Name
// UnmarshalXML appends the property names enclosed within start to pn.
//
// It returns an error if start does not contain any properties or if
// properties contain values. Character data between properties is ignored.
func (pn *propfindProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error {
for {
t, err := next(d)
if err != nil {
return err
}
switch t.(type) {
case ixml.EndElement:
if len(*pn) == 0 {
return fmt.Errorf("%s must not be empty", start.Name.Local)
}
return nil
case ixml.StartElement:
name := t.(ixml.StartElement).Name
t, err = next(d)
if err != nil {
return err
}
if _, ok := t.(ixml.EndElement); !ok {
return fmt.Errorf("unexpected token %T", t)
}
*pn = append(*pn, xml.Name(name))
}
}
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind
type propfind struct {
XMLName ixml.Name `xml:"DAV: propfind"`
Allprop *struct{} `xml:"DAV: allprop"`
Propname *struct{} `xml:"DAV: propname"`
Prop propfindProps `xml:"DAV: prop"`
Include propfindProps `xml:"DAV: include"`
}
func readPropfind(r io.Reader) (pf propfind, status int, err error) {
c := countingReader{r: r}
if err = ixml.NewDecoder(&c).Decode(&pf); err != nil {
if err == io.EOF {
if c.n == 0 {
// An empty body means to propfind allprop.
// http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
return propfind{Allprop: new(struct{})}, 0, nil
}
err = errInvalidPropfind
}
return propfind{}, http.StatusBadRequest, err
}
if pf.Allprop == nil && pf.Include != nil {
return propfind{}, http.StatusBadRequest, errInvalidPropfind
}
if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) {
return propfind{}, http.StatusBadRequest, errInvalidPropfind
}
if pf.Prop != nil && pf.Propname != nil {
return propfind{}, http.StatusBadRequest, errInvalidPropfind
}
if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil {
return propfind{}, http.StatusBadRequest, errInvalidPropfind
}
return pf, 0, nil
}
// Property represents a single DAV resource property as defined in RFC 4918.
// See http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties
type Property struct {
// XMLName is the fully qualified name that identifies this property.
XMLName xml.Name
// Lang is an optional xml:lang attribute.
Lang string `xml:"xml:lang,attr,omitempty"`
// InnerXML contains the XML representation of the property value.
// See http://www.webdav.org/specs/rfc4918.html#property_values
//
// Property values of complex type or mixed-content must have fully
// expanded XML namespaces or be self-contained with according
// XML namespace declarations. They must not rely on any XML
// namespace declarations within the scope of the XML document,
// even including the DAV: namespace.
InnerXML []byte `xml:",innerxml"`
}
// ixmlProperty is the same as the Property type except it holds an ixml.Name
// instead of an xml.Name.
type ixmlProperty struct {
XMLName ixml.Name
Lang string `xml:"xml:lang,attr,omitempty"`
InnerXML []byte `xml:",innerxml"`
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
// See multistatusWriter for the "D:" namespace prefix.
type xmlError struct {
XMLName ixml.Name `xml:"D:error"`
InnerXML []byte `xml:",innerxml"`
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat
// See multistatusWriter for the "D:" namespace prefix.
type propstat struct {
Prop []Property `xml:"D:prop>_ignored_"`
Status string `xml:"D:status"`
Error *xmlError `xml:"D:error"`
ResponseDescription string `xml:"D:responsedescription,omitempty"`
}
// ixmlPropstat is the same as the propstat type except it holds an ixml.Name
// instead of an xml.Name.
type ixmlPropstat struct {
Prop []ixmlProperty `xml:"D:prop>_ignored_"`
Status string `xml:"D:status"`
Error *xmlError `xml:"D:error"`
ResponseDescription string `xml:"D:responsedescription,omitempty"`
}
// MarshalXML prepends the "D:" namespace prefix on properties in the DAV: namespace
// before encoding. See multistatusWriter.
func (ps propstat) MarshalXML(e *ixml.Encoder, start ixml.StartElement) error {
// Convert from a propstat to an ixmlPropstat.
ixmlPs := ixmlPropstat{
Prop: make([]ixmlProperty, len(ps.Prop)),
Status: ps.Status,
Error: ps.Error,
ResponseDescription: ps.ResponseDescription,
}
for k, prop := range ps.Prop {
ixmlPs.Prop[k] = ixmlProperty{
XMLName: ixml.Name(prop.XMLName),
Lang: prop.Lang,
InnerXML: prop.InnerXML,
}
}
for k, prop := range ixmlPs.Prop {
if prop.XMLName.Space == "DAV:" {
prop.XMLName = ixml.Name{Space: "", Local: "D:" + prop.XMLName.Local}
ixmlPs.Prop[k] = prop
}
}
// Distinct type to avoid infinite recursion of MarshalXML.
type newpropstat ixmlPropstat
return e.EncodeElement(newpropstat(ixmlPs), start)
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_response
// See multistatusWriter for the "D:" namespace prefix.
type response struct {
XMLName ixml.Name `xml:"D:response"`
Href []string `xml:"D:href"`
Propstat []propstat `xml:"D:propstat"`
Status string `xml:"D:status,omitempty"`
Error *xmlError `xml:"D:error"`
ResponseDescription string `xml:"D:responsedescription,omitempty"`
}
// MultistatusWriter marshals one or more Responses into a XML
// multistatus response.
// See http://www.webdav.org/specs/rfc4918.html#ELEMENT_multistatus
// TODO(rsto, mpl): As a workaround, the "D:" namespace prefix, defined as
// "DAV:" on this element, is prepended on the nested response, as well as on all
// its nested elements. All property names in the DAV: namespace are prefixed as
// well. This is because some versions of Mini-Redirector (on windows 7) ignore
// elements with a default namespace (no prefixed namespace). A less intrusive fix
// should be possible after golang.org/cl/11074. See https://golang.org/issue/11177
type multistatusWriter struct {
// ResponseDescription contains the optional responsedescription
// of the multistatus XML element. Only the latest content before
// close will be emitted. Empty response descriptions are not
// written.
responseDescription string
w http.ResponseWriter
enc *ixml.Encoder
}
// Write validates and emits a DAV response as part of a multistatus response
// element.
//
// It sets the HTTP status code of its underlying http.ResponseWriter to 207
// (Multi-Status) and populates the Content-Type header. If r is the
// first, valid response to be written, Write prepends the XML representation
// of r with a multistatus tag. Callers must call close after the last response
// has been written.
func (w *multistatusWriter) write(r *response) error {
switch len(r.Href) {
case 0:
return errInvalidResponse
case 1:
if len(r.Propstat) > 0 != (r.Status == "") {
return errInvalidResponse
}
default:
if len(r.Propstat) > 0 || r.Status == "" {
return errInvalidResponse
}
}
err := w.writeHeader()
if err != nil {
return err
}
return w.enc.Encode(r)
}
// writeHeader writes a XML multistatus start element on w's underlying
// http.ResponseWriter and returns the result of the write operation.
// After the first write attempt, writeHeader becomes a no-op.
func (w *multistatusWriter) writeHeader() error {
if w.enc != nil {
return nil
}
w.w.Header().Add("Content-Type", "text/xml; charset=utf-8")
w.w.WriteHeader(StatusMulti)
_, err := fmt.Fprintf(w.w, `<?xml version="1.0" encoding="UTF-8"?>`)
if err != nil {
return err
}
w.enc = ixml.NewEncoder(w.w)
return w.enc.EncodeToken(ixml.StartElement{
Name: ixml.Name{
Space: "DAV:",
Local: "multistatus",
},
Attr: []ixml.Attr{{
Name: ixml.Name{Space: "xmlns", Local: "D"},
Value: "DAV:",
}},
})
}
// Close completes the marshalling of the multistatus response. It returns
// an error if the multistatus response could not be completed. If both the
// return value and field enc of w are nil, then no multistatus response has
// been written.
func (w *multistatusWriter) close() error {
if w.enc == nil {
return nil
}
var end []ixml.Token
if w.responseDescription != "" {
name := ixml.Name{Space: "DAV:", Local: "responsedescription"}
end = append(end,
ixml.StartElement{Name: name},
ixml.CharData(w.responseDescription),
ixml.EndElement{Name: name},
)
}
end = append(end, ixml.EndElement{
Name: ixml.Name{Space: "DAV:", Local: "multistatus"},
})
for _, t := range end {
err := w.enc.EncodeToken(t)
if err != nil {
return err
}
}
return w.enc.Flush()
}
var xmlLangName = ixml.Name{Space: "http://www.w3.org/XML/1998/namespace", Local: "lang"}
func xmlLang(s ixml.StartElement, d string) string {
for _, attr := range s.Attr {
if attr.Name == xmlLangName {
return attr.Value
}
}
return d
}
type xmlValue []byte
func (v *xmlValue) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error {
// The XML value of a property can be arbitrary, mixed-content XML.
// To make sure that the unmarshalled value contains all required
// namespaces, we encode all the property value XML tokens into a
// buffer. This forces the encoder to redeclare any used namespaces.
var b bytes.Buffer
e := ixml.NewEncoder(&b)
for {
t, err := next(d)
if err != nil {
return err
}
if e, ok := t.(ixml.EndElement); ok && e.Name == start.Name {
break
}
if err = e.EncodeToken(t); err != nil {
return err
}
}
err := e.Flush()
if err != nil {
return err
}
*v = b.Bytes()
return nil
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for proppatch)
type proppatchProps []Property
// UnmarshalXML appends the property names and values enclosed within start
// to ps.
//
// An xml:lang attribute that is defined either on the DAV:prop or property
// name XML element is propagated to the property's Lang field.
//
// UnmarshalXML returns an error if start does not contain any properties or if
// property values contain syntactically incorrect XML.
func (ps *proppatchProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error {
lang := xmlLang(start, "")
for {
t, err := next(d)
if err != nil {
return err
}
switch elem := t.(type) {
case ixml.EndElement:
if len(*ps) == 0 {
return fmt.Errorf("%s must not be empty", start.Name.Local)
}
return nil
case ixml.StartElement:
p := Property{
XMLName: xml.Name(t.(ixml.StartElement).Name),
Lang: xmlLang(t.(ixml.StartElement), lang),
}
err = d.DecodeElement(((*xmlValue)(&p.InnerXML)), &elem)
if err != nil {
return err
}
*ps = append(*ps, p)
}
}
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_set
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_remove
type setRemove struct {
XMLName ixml.Name
Lang string `xml:"xml:lang,attr,omitempty"`
Prop proppatchProps `xml:"DAV: prop"`
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propertyupdate
type propertyupdate struct {
XMLName ixml.Name `xml:"DAV: propertyupdate"`
Lang string `xml:"xml:lang,attr,omitempty"`
SetRemove []setRemove `xml:",any"`
}
func readProppatch(r io.Reader) (patches []Proppatch, status int, err error) {
var pu propertyupdate
if err = ixml.NewDecoder(r).Decode(&pu); err != nil {
return nil, http.StatusBadRequest, err
}
for _, op := range pu.SetRemove {
remove := false
switch op.XMLName {
case ixml.Name{Space: "DAV:", Local: "set"}:
// No-op.
case ixml.Name{Space: "DAV:", Local: "remove"}:
for _, p := range op.Prop {
if len(p.InnerXML) > 0 {
return nil, http.StatusBadRequest, errInvalidProppatch
}
}
remove = true
default:
return nil, http.StatusBadRequest, errInvalidProppatch
}
patches = append(patches, Proppatch{Remove: remove, Props: op.Prop})
}
return patches, 0, nil
}
-32
View File
@@ -1,32 +0,0 @@
package test
import (
"fmt"
"github.com/Xhofe/alist/utils"
"path/filepath"
"strings"
"testing"
)
func TestSplit(t *testing.T) {
drive_id := "/123/456"
strs := strings.Split(drive_id, "/")
fmt.Println(strs)
}
func TestPassword(t *testing.T) {
fullName := "hello.password-xhf"
index := strings.Index(fullName, ".password-")
name := fullName[:index]
password := fullName[index+10:]
fmt.Printf("name:%s, password:%s\n", name, password)
}
func TestDir(t *testing.T) {
dir, file := filepath.Split("root")
fmt.Printf("dir:%s\nfile:%s\n", dir, file)
}
func TestMD5(t *testing.T) {
fmt.Printf("%s\n", utils.Get16MD5Encode("123456"))
}
-16
View File
@@ -1,16 +0,0 @@
package test
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
"testing"
)
func TestStr(t *testing.T) {
fmt.Println(".password-"[10:])
}
func TestWriteYml(t *testing.T) {
utils.WriteToYml("../conf.yml", conf.Conf)
}
+59
View File
@@ -0,0 +1,59 @@
package utils
import (
"bytes"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"io/ioutil"
"unicode/utf8"
)
func IsGBK(data []byte) bool {
length := len(data)
var i = 0
for i < length {
if data[i] <= 0x7f {
//编码0~127,只有一个字节的编码,兼容ASCII码
i++
continue
} else {
//大于127的使用双字节编码,落在gbk编码范围内的字符
if data[i] >= 0x81 &&
data[i] <= 0xfe &&
data[i+1] >= 0x40 &&
data[i+1] <= 0xfe &&
data[i+1] != 0xf7 {
i += 2
continue
} else {
return false
}
}
}
return true
}
const (
GBK string = "GBK"
UTF8 string = "UTF8"
UNKNOWN string = "UNKNOWN"
)
func GetStrCoding(data []byte) string {
if utf8.Valid(data) {
return UTF8
} else if IsGBK(data) {
return GBK
} else {
return UNKNOWN
}
}
func GbkToUtf8(s []byte) ([]byte, error) {
reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder())
d, e := ioutil.ReadAll(reader)
if e != nil {
return nil, e
}
return d, nil
}
-57
View File
@@ -1,57 +0,0 @@
package utils
import (
"errors"
"fmt"
"reflect"
)
// copy interface val
func SimpleCopyProperties(dst, src interface{}) (err error) {
// 防止意外panic
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("%v", e)
}
}()
dstType, dstValue := reflect.TypeOf(dst), reflect.ValueOf(dst)
srcType, srcValue := reflect.TypeOf(src), reflect.ValueOf(src)
// dst必须结构体指针类型
if dstType.Kind() != reflect.Ptr || dstType.Elem().Kind() != reflect.Struct {
return errors.New("dst type should be a struct pointer")
}
// src必须为结构体或者结构体指针,.Elem()类似于*ptr的操作返回指针指向的地址反射类型
if srcType.Kind() == reflect.Ptr {
srcType, srcValue = srcType.Elem(), srcValue.Elem()
}
if srcType.Kind() != reflect.Struct {
return errors.New("src type should be a struct or a struct pointer")
}
// 取具体内容
dstType, dstValue = dstType.Elem(), dstValue.Elem()
// 属性个数
propertyNums := dstType.NumField()
for i := 0; i < propertyNums; i++ {
// 属性
property := dstType.Field(i)
// 待填充属性值
propertyValue := srcValue.FieldByName(property.Name)
// 无效,说明src没有这个属性 || 属性同名但类型不同
if !propertyValue.IsValid() || property.Type != propertyValue.Type() {
continue
}
if dstValue.Field(i).CanSet() {
dstValue.Field(i).Set(propertyValue)
}
}
return nil
}
-22
View File
@@ -1,22 +0,0 @@
package utils
import "github.com/Xhofe/alist/conf"
func GetDriveByName(name string) *conf.Drive {
for i, drive := range conf.Conf.AliDrive.Drives{
if drive.Name == name {
return &conf.Conf.AliDrive.Drives[i]
}
}
return nil
}
func GetNames() []string {
res := make([]string, 0)
for _, drive := range conf.Conf.AliDrive.Drives{
if !drive.Hide {
res = append(res, drive.Name)
}
}
return res
}
+62 -9
View File
@@ -1,14 +1,16 @@
package utils package utils
import ( import (
"encoding/json"
"github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
) )
// determine whether the file exists // Exists determine whether the file exists
func Exists(name string) bool { func Exists(name string) bool {
if _, err := os.Stat(name); err != nil { if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@@ -18,27 +20,78 @@ func Exists(name string) bool {
return true return true
} }
// 嵌套创建文件 // IsDir determine whether the file is dir
func IsDir(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}
// GetFileType get file type
func GetFileType(ext string) int {
if ext == "" {
return conf.UNKNOWN
}
ext = strings.ToLower(strings.TrimLeft(ext,"."))
if IsContain(conf.OfficeTypes, ext) {
return conf.OFFICE
}
if IsContain(conf.AudioTypes, ext) {
return conf.AUDIO
}
if IsContain(conf.VideoTypes, ext) {
return conf.VIDEO
}
if IsContain(conf.TextTypes, ext) {
return conf.TEXT
}
if IsContain(conf.ImageTypes, ext) {
return conf.IMAGE
}
return conf.UNKNOWN
}
// CreatNestedFile create nested file
func CreatNestedFile(path string) (*os.File, error) { func CreatNestedFile(path string) (*os.File, error) {
basePath := filepath.Dir(path) basePath := filepath.Dir(path)
if !Exists(basePath) { if !Exists(basePath) {
err := os.MkdirAll(basePath, 0700) err := os.MkdirAll(basePath, 0700)
if err != nil { if err != nil {
log.Errorf("无法创建目录%s", err) log.Errorf("can't create foler%s", err)
return nil, err return nil, err
} }
} }
return os.Create(path) return os.Create(path)
} }
// write struct to yaml file // WriteToJson write struct to json file
func WriteToYml(src string, conf interface{}) { func WriteToJson(src string, conf interface{}) bool {
data, err := yaml.Marshal(conf) data, err := json.MarshalIndent(conf, "", " ")
if err != nil { if err != nil {
log.Errorf("Conf转[]byte失败:%s", err.Error()) log.Errorf("failed convert Conf to []byte:%s", err.Error())
return false
} }
err = ioutil.WriteFile(src, data, 0777) err = ioutil.WriteFile(src, data, 0777)
if err != nil { if err != nil {
log.Errorf("写yml文件失败", err.Error()) log.Errorf("failed to write json file:%s", err.Error())
return false
} }
return true
}
func ParsePath(path string) string {
path = strings.TrimRight(path, "/")
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
return path
}
func RemoveLastSlash(path string) string {
if len(path) > 1 {
return strings.TrimSuffix(path, "/")
}
return path
} }
+2 -2
View File
@@ -5,14 +5,14 @@ import (
"encoding/hex" "encoding/hex"
) )
//返回一个32位md5加密后的字符串 // GetMD5Encode
func GetMD5Encode(data string) string { func GetMD5Encode(data string) string {
h := md5.New() h := md5.New()
h.Write([]byte(data)) h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil)) return hex.EncodeToString(h.Sum(nil))
} }
//返回一个16位md5加密后的字符串 // Get16MD5Encode
func Get16MD5Encode(data string) string { func Get16MD5Encode(data string) string {
return GetMD5Encode(data)[8:24] return GetMD5Encode(data)[8:24]
} }
+10
View File
@@ -0,0 +1,10 @@
package utils
func IsContain(items []string, item string) bool {
for _, eachItem := range items {
if eachItem == item {
return true
}
}
return false
}
-25
View File
@@ -1,35 +1,10 @@
package utils package utils
import ( import (
log "github.com/sirupsen/logrus"
"net/url"
"strconv" "strconv"
"strings" "strings"
) )
// get code from url
func GetCode(rawUrl string) string {
u, err := url.Parse(rawUrl)
if err != nil {
log.Errorf("解析url出错:%s", err.Error())
return ""
}
code := u.Query().Get("code")
return code
}
// determine whether to include
func ContainsString(array []string, val string) (index int) {
index = -1
for i := 0; i < len(array); i++ {
if array[i] == val {
index = i
return
}
}
return
}
// compare version // compare version
func VersionCompare(version1, version2 string) int { func VersionCompare(version1, version2 string) int {
a := strings.Split(version1, ".") a := strings.Split(version1, ".")