├── 115 ├── api.go ├── client.go ├── crypto.go └── types.go ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yaml ├── Dockerfile ├── README.md ├── common ├── config │ └── config.go ├── drive │ └── interface.go └── errors.go ├── config.json.example ├── go.mod ├── go.sum ├── main.go ├── systemd.service.example └── webdav ├── file.go ├── if.go ├── internal └── xml │ ├── marshal.go │ ├── read.go │ ├── typeinfo.go │ └── xml.go ├── lock.go ├── prop.go ├── webdav.go └── xml.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | pull_request: 8 | branches: 9 | - "main" 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v3 19 | with: 20 | go-version: 1.18 21 | 22 | - name: Build 23 | run: go build -v ./... 24 | 25 | - name: Test 26 | run: go test -v ./... 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - name: Set up Go 15 | uses: actions/setup-go@v3 16 | with: 17 | go-version: 1.18 18 | 19 | - name: Login to Docker Hub 20 | uses: docker/login-action@v2 21 | with: 22 | username: ${{ secrets.DOCKERHUB_USERNAME }} 23 | password: ${{ secrets.DOCKERHUB_TOKEN }} 24 | 25 | - name: Run GoReleaser 26 | uses: goreleaser/goreleaser-action@v3 27 | with: 28 | version: v1.15.2 29 | args: release --clean 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /115drive-webdav 2 | 3 | dist/ 4 | 5 | dist/ 6 | 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | flags: 13 | - -trimpath 14 | ldflags: 15 | - -s -w 16 | goos: 17 | - linux 18 | - windows 19 | - darwin 20 | goarch: 21 | - amd64 22 | - 386 23 | - arm 24 | - arm64 25 | - mips 26 | - mipsle 27 | - mips64 28 | - mips64le 29 | goarm: 30 | - 5 31 | - 6 32 | - 7 33 | gomips: 34 | - hardfloat 35 | - softfloat 36 | ignore: 37 | - goos: windows 38 | goarch: arm 39 | - goos: windows 40 | goarch: arm64 41 | archives: 42 | - replacements: 43 | darwin: Darwin 44 | linux: Linux 45 | windows: Windows 46 | 386: i386 47 | amd64: x86_64 48 | checksum: 49 | name_template: 'checksums.txt' 50 | dockers: 51 | - use: buildx 52 | goarch: amd64 53 | build_flag_templates: 54 | - "--platform=linux/amd64" 55 | image_templates: 56 | - "gaoyb7/115drive-webdav:{{ .Tag }}-amd64" 57 | - use: buildx 58 | goarch: 386 59 | build_flag_templates: 60 | - "--platform=linux/386" 61 | image_templates: 62 | - "gaoyb7/115drive-webdav:{{ .Tag }}-i386" 63 | - use: buildx 64 | goarch: arm64 65 | build_flag_templates: 66 | - "--platform=linux/arm64/v8" 67 | image_templates: 68 | - "gaoyb7/115drive-webdav:{{ .Tag }}-arm64v8" 69 | - use: buildx 70 | goarch: arm 71 | goarm: 7 72 | build_flag_templates: 73 | - "--platform=linux/arm/v7" 74 | image_templates: 75 | - "gaoyb7/115drive-webdav:{{ .Tag }}-armv7" 76 | - use: buildx 77 | goarch: arm 78 | goarm: 6 79 | build_flag_templates: 80 | - "--platform=linux/arm/v6" 81 | image_templates: 82 | - "gaoyb7/115drive-webdav:{{ .Tag }}-armv6" 83 | docker_manifests: 84 | - name_template: gaoyb7/115drive-webdav:{{ .Tag }} 85 | image_templates: 86 | - gaoyb7/115drive-webdav:{{ .Tag }}-amd64 87 | - gaoyb7/115drive-webdav:{{ .Tag }}-i386 88 | - gaoyb7/115drive-webdav:{{ .Tag }}-arm64v8 89 | - gaoyb7/115drive-webdav:{{ .Tag }}-armv7 90 | - gaoyb7/115drive-webdav:{{ .Tag }}-armv6 91 | - name_template: gaoyb7/115drive-webdav:latest 92 | image_templates: 93 | - gaoyb7/115drive-webdav:{{ .Tag }}-amd64 94 | - gaoyb7/115drive-webdav:{{ .Tag }}-i386 95 | - gaoyb7/115drive-webdav:{{ .Tag }}-arm64v8 96 | - gaoyb7/115drive-webdav:{{ .Tag }}-armv7 97 | - gaoyb7/115drive-webdav:{{ .Tag }}-armv6 98 | snapshot: 99 | name_template: "{{ incpatch .Tag }}" 100 | changelog: 101 | sort: asc 102 | filters: 103 | exclude: 104 | - '^docs:' 105 | - '^test:' 106 | -------------------------------------------------------------------------------- /115/api.go: -------------------------------------------------------------------------------- 1 | package _115 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "github.com/gaoyb7/115drive-webdav/common" 11 | "github.com/go-resty/resty/v2" 12 | ) 13 | 14 | const ( 15 | UserAgent = "Mozilla/5.0 115Browser/23.9.3.2" 16 | 17 | APIURLGetFiles = "https://webapi.115.com/files" 18 | APIURLGetDownloadURL = "https://proapi.115.com/app/chrome/downurl" 19 | APIURLGetDirID = "https://webapi.115.com/files/getid" 20 | APIURLDeleteFile = "https://webapi.115.com/rb/delete" 21 | APIURLAddDir = "https://webapi.115.com/files/add" 22 | APIURLMoveFile = "https://webapi.115.com/files/move" 23 | APIURLRenameFile = "https://webapi.115.com/files/batch_rename" 24 | APIURLLoginCheck = "https://passportapi.115.com/app/1.0/web/1.0/check/sso" 25 | ) 26 | 27 | func APIGetFiles(client *resty.Client, cid string, pageSize int64, offset int64) (*APIGetFilesResp, error) { 28 | result := APIGetFilesResp{} 29 | _, err := client.R(). 30 | SetQueryParams(map[string]string{ 31 | "aid": "1", 32 | "cid": cid, 33 | "o": "user_ptime", 34 | "asc": "0", 35 | "offset": strconv.FormatInt(offset, 10), 36 | "show_dir": "1", 37 | "limit": strconv.FormatInt(pageSize, 10), 38 | "snap": "0", 39 | "record_open_time": "1", 40 | "format": "json", 41 | "fc_mix": "0", 42 | }). 43 | SetResult(&result). 44 | ForceContentType("application/json"). 45 | Get(APIURLGetFiles) 46 | if err != nil { 47 | return nil, fmt.Errorf("api get files fail, err: %v", err) 48 | } 49 | 50 | return &result, nil 51 | } 52 | 53 | func APIGetDownloadURL(client *resty.Client, pickCode string) (*DownloadInfo, error) { 54 | key := GenerateKey() 55 | params, _ := json.Marshal(map[string]string{"pickcode": pickCode}) 56 | 57 | result := APIBaseResp{} 58 | _, err := client.R(). 59 | SetQueryParam("t", strconv.FormatInt(time.Now().Unix(), 10)). 60 | SetFormData(map[string]string{ 61 | "data": string(Encode(params, key)), 62 | }). 63 | SetResult(&result). 64 | ForceContentType("application/json"). 65 | Post(APIURLGetDownloadURL) 66 | if err != nil { 67 | return nil, fmt.Errorf("api get download url fail, err: %v", err) 68 | } 69 | 70 | var encodedData string 71 | if err = json.Unmarshal(result.Data, &encodedData); err != nil { 72 | return nil, fmt.Errorf("api get download url, call json.Unmarshal fail, body: %s", string(result.Data)) 73 | } 74 | decodedData, err := Decode(encodedData, key) 75 | if err != nil { 76 | return nil, fmt.Errorf("api get download url, call Decode fail, err: %w", err) 77 | } 78 | 79 | resp := DownloadData{} 80 | if err := json.Unmarshal(decodedData, &resp); err != nil { 81 | return nil, fmt.Errorf("api get download url, call json.Unmarshal fail, body: %s", string(decodedData)) 82 | } 83 | 84 | for _, info := range resp { 85 | fileSize, _ := info.FileSize.Int64() 86 | if fileSize == 0 { 87 | return nil, common.ErrNotFound 88 | } 89 | return info, nil 90 | } 91 | 92 | return nil, nil 93 | } 94 | 95 | func APIGetDirID(client *resty.Client, dir string) (*APIGetDirIDResp, error) { 96 | if strings.HasPrefix(dir, "/") { 97 | dir = dir[1:] 98 | } 99 | 100 | result := APIGetDirIDResp{} 101 | _, err := client.R(). 102 | SetQueryParam("path", dir). 103 | SetResult(&result). 104 | ForceContentType("application/json"). 105 | Get(APIURLGetDirID) 106 | if err != nil { 107 | return nil, fmt.Errorf("api get dir id fail, err: %v", err) 108 | } 109 | 110 | return &result, nil 111 | } 112 | 113 | func APIDeleteFile(client *resty.Client, fid string, pid string) (*APIDeleteFileResp, error) { 114 | result := APIDeleteFileResp{} 115 | _, err := client.R(). 116 | SetFormData(map[string]string{ 117 | "fid[0]": fid, 118 | "pid": pid, 119 | "ignore_warn": "1", 120 | }). 121 | SetResult(&result). 122 | ForceContentType("application/json"). 123 | Post(APIURLDeleteFile) 124 | if err != nil { 125 | return nil, fmt.Errorf("api delete file fail, err: %v", err) 126 | } 127 | 128 | return &result, nil 129 | } 130 | 131 | func APIAddDir(client *resty.Client, pid string, cname string) (*APIAddDirResp, error) { 132 | result := APIAddDirResp{} 133 | _, err := client.R(). 134 | SetFormData(map[string]string{ 135 | "pid": pid, 136 | "cname": cname, 137 | }). 138 | SetResult(&result). 139 | ForceContentType("application/json"). 140 | Post(APIURLAddDir) 141 | if err != nil { 142 | return nil, fmt.Errorf("api add dir fail, err: %v", err) 143 | } 144 | 145 | return &result, nil 146 | } 147 | 148 | func APIMoveFile(client *resty.Client, fid string, pid string) (*APIMoveFileResp, error) { 149 | result := APIMoveFileResp{} 150 | _, err := client.R(). 151 | SetFormData(map[string]string{ 152 | "fid[0]": fid, 153 | "pid": pid, 154 | }). 155 | SetResult(&result). 156 | ForceContentType("application/json"). 157 | Post(APIURLMoveFile) 158 | if err != nil { 159 | return nil, fmt.Errorf("api move file fail, err: %v", err) 160 | } 161 | 162 | return &result, nil 163 | } 164 | 165 | func APIRenameFile(client *resty.Client, fid string, name string) (*APIRenameFileResp, error) { 166 | result := APIRenameFileResp{} 167 | _, err := client.R(). 168 | SetFormData(map[string]string{ 169 | fmt.Sprintf("files_new_name[%s]", fid): name, 170 | }). 171 | SetResult(&result). 172 | ForceContentType("application/json"). 173 | Post(APIURLRenameFile) 174 | if err != nil { 175 | return nil, fmt.Errorf("api rename file fail, err: %v", err) 176 | } 177 | 178 | return &result, nil 179 | 180 | } 181 | 182 | func APILoginCheck(client *resty.Client) (int64, error) { 183 | result := APILoginCheckResp{} 184 | _, err := client.R(). 185 | SetResult(&result). 186 | ForceContentType("application/json"). 187 | Get(APIURLLoginCheck) 188 | if err != nil { 189 | return 0, fmt.Errorf("api login check fail, err: %v", err) 190 | } 191 | 192 | userID, _ := result.Data.UserID.Int64() 193 | return userID, nil 194 | } 195 | -------------------------------------------------------------------------------- /115/client.go: -------------------------------------------------------------------------------- 1 | package _115 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "net/http/httputil" 9 | "net/url" 10 | "path" 11 | "strings" 12 | "time" 13 | 14 | "github.com/bluele/gcache" 15 | "github.com/gaoyb7/115drive-webdav/common" 16 | "github.com/gaoyb7/115drive-webdav/common/drive" 17 | "github.com/go-resty/resty/v2" 18 | "github.com/sirupsen/logrus" 19 | "golang.org/x/time/rate" 20 | ) 21 | 22 | type DriveClient struct { 23 | HttpClient *resty.Client 24 | cache gcache.Cache 25 | reserveProxy *httputil.ReverseProxy 26 | limiter *rate.Limiter 27 | } 28 | 29 | func MustNew115DriveClient(uid string, cid string, seid string, kid string) *DriveClient { 30 | httpClient := resty.New().SetCookie(&http.Cookie{ 31 | Name: "UID", 32 | Value: uid, 33 | Domain: "www.115.com", 34 | Path: "/", 35 | HttpOnly: true, 36 | }).SetCookie(&http.Cookie{ 37 | Name: "CID", 38 | Value: cid, 39 | Domain: "www.115.com", 40 | Path: "/", 41 | HttpOnly: true, 42 | }).SetCookie(&http.Cookie{ 43 | Name: "SEID", 44 | Value: seid, 45 | Domain: "www.115.com", 46 | Path: "/", 47 | HttpOnly: true, 48 | }).SetCookie(&http.Cookie{ 49 | Name: "KID", 50 | Value: kid, 51 | Domain: "www.115.com", 52 | Path: "/", 53 | HttpOnly: true, 54 | }).SetHeader("User-Agent", UserAgent) 55 | 56 | client := &DriveClient{ 57 | HttpClient: httpClient, 58 | cache: gcache.New(10000).LFU().Build(), 59 | limiter: rate.NewLimiter(5, 1), 60 | reserveProxy: &httputil.ReverseProxy{ 61 | Transport: httpClient.GetClient().Transport, 62 | Director: func(req *http.Request) { 63 | req.Header.Set("Referer", "https://115.com/") 64 | req.Header.Set("User-Agent", UserAgent) 65 | req.Header.Set("Host", req.Host) 66 | }, 67 | }, 68 | } 69 | 70 | // login check 71 | userID, err := APILoginCheck(client.HttpClient) 72 | if err != nil || userID <= 0 { 73 | logrus.WithError(err).Panicf("115 drive login fail") 74 | } 75 | logrus.Infof("115 drive login succ, user_id: %d", userID) 76 | 77 | return client 78 | } 79 | 80 | func (c *DriveClient) GetFiles(dir string) ([]drive.File, error) { 81 | dir = slashClean(dir) 82 | cacheKey := fmt.Sprintf("files:%s", dir) 83 | if value, err := c.cache.Get(cacheKey); err == nil { 84 | return value.([]drive.File), nil 85 | } 86 | 87 | c.limiter.Wait(context.Background()) 88 | getDirIDResp, err := APIGetDirID(c.HttpClient, dir) 89 | if err != nil { 90 | return nil, err 91 | } 92 | cid := getDirIDResp.CategoryID.String() 93 | 94 | pageSize := int64(1000) 95 | offset := int64(0) 96 | files := make([]drive.File, 0) 97 | for { 98 | resp, err := APIGetFiles(c.HttpClient, cid, pageSize, offset) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | for idx := range resp.Data { 104 | files = append(files, &resp.Data[idx]) 105 | } 106 | 107 | offset = resp.Offset + pageSize 108 | if offset >= resp.Count { 109 | break 110 | } 111 | } 112 | if err := c.cache.SetWithExpire(cacheKey, files, time.Minute*2); err != nil { 113 | logrus.WithError(err).Errorf("call c.cache.SetWithExpire fail, dir: %s", dir) 114 | } 115 | 116 | return files, nil 117 | } 118 | 119 | func (c *DriveClient) GetFile(filePath string) (drive.File, error) { 120 | filePath = slashClean(filePath) 121 | if filePath == "/" || len(filePath) == 0 { 122 | return &FileInfo{CategoryID: "0"}, nil 123 | } 124 | 125 | filePath = strings.TrimRight(filePath, "/") 126 | dir, fileName := path.Split(filePath) 127 | 128 | files, err := c.GetFiles(dir) 129 | if err != nil { 130 | return nil, err 131 | } 132 | for _, file := range files { 133 | if file.GetName() == fileName { 134 | return file, nil 135 | } 136 | } 137 | 138 | return nil, common.ErrNotFound 139 | } 140 | 141 | func (c *DriveClient) ServeContent(w http.ResponseWriter, req *http.Request, fi drive.File) { 142 | fileURL, err := c.GetFileURL(fi) 143 | if err != nil { 144 | w.WriteHeader(http.StatusInternalServerError) 145 | w.Write([]byte(http.StatusText(http.StatusInternalServerError))) 146 | return 147 | } 148 | 149 | logrus.Infof("proxy open [name: %v] [url: %v] [range: %v]", fi.GetName(), fileURL, req.Header.Get("Range")) 150 | req.Header.Del("If-Match") 151 | c.Proxy(w, req, fileURL) 152 | } 153 | 154 | func (c *DriveClient) GetFileURL(file drive.File) (string, error) { 155 | pickCode := file.(*FileInfo).PickCode 156 | cacheKey := fmt.Sprintf("url:%s", pickCode) 157 | if value, err := c.cache.Get(cacheKey); err == nil { 158 | return value.(string), nil 159 | } 160 | 161 | c.limiter.Wait(context.Background()) 162 | info, err := APIGetDownloadURL(c.HttpClient, pickCode) 163 | if err != nil { 164 | return "", err 165 | } 166 | 167 | if err := c.cache.SetWithExpire(cacheKey, info.URL.URL, time.Minute*2); err != nil { 168 | logrus.WithError(err).Errorf("call c.cache.SetWithExpire fail, url: %s", info.URL.URL) 169 | } 170 | 171 | return info.URL.URL, nil 172 | } 173 | 174 | func (c *DriveClient) RemoveFile(filePath string) error { 175 | c.limiter.Wait(context.Background()) 176 | fi, err := c.GetFile(filePath) 177 | if err != nil { 178 | return err 179 | } 180 | fid := fi.(*FileInfo).FileID.String() 181 | if fi.IsDir() { 182 | fid = fi.(*FileInfo).CategoryID.String() 183 | } 184 | 185 | pid := fi.(*FileInfo).ParentID.String() 186 | if err != nil { 187 | return err 188 | } 189 | 190 | resp, err := APIDeleteFile(c.HttpClient, fid, pid) 191 | if err != nil { 192 | return err 193 | } 194 | 195 | if !resp.State { 196 | return fmt.Errorf("remove file fail, state is false") 197 | } 198 | 199 | filePath = slashClean(filePath) 200 | filePath = strings.TrimRight(filePath, "/") 201 | dir, _ := path.Split(filePath) 202 | c.flushDir(dir) 203 | 204 | return nil 205 | } 206 | 207 | func (c *DriveClient) MakeDir(dir string) error { 208 | c.limiter.Wait(context.Background()) 209 | getDirIDResp, err := APIGetDirID(c.HttpClient, dir) 210 | if err != nil { 211 | return err 212 | } 213 | cid, _ := getDirIDResp.CategoryID.Int64() 214 | if cid != 0 { 215 | logrus.WithField("dir", dir).Infof("dir exists, ignore") 216 | return nil 217 | } 218 | 219 | dir = slashClean(dir) 220 | parentDir, name := path.Split(dir) 221 | getDirIDResp, err = APIGetDirID(c.HttpClient, parentDir) 222 | if err != nil { 223 | return err 224 | } 225 | 226 | pid := getDirIDResp.CategoryID.String() 227 | if pid == "0" && parentDir != "/" { 228 | return nil 229 | } 230 | resp, err := APIAddDir(c.HttpClient, pid, name) 231 | if err != nil { 232 | return err 233 | } 234 | if !resp.State { 235 | return fmt.Errorf("new dir fail, state is false") 236 | } 237 | 238 | c.flushDir(parentDir) 239 | return nil 240 | } 241 | 242 | func (c *DriveClient) MoveFile(srcPath string, dstPath string) error { 243 | logrus.Infof("move file, src: %s, dst: %s", srcPath, dstPath) 244 | 245 | c.limiter.Wait(context.Background()) 246 | fi, err := c.GetFile(srcPath) 247 | if err != nil { 248 | return err 249 | } 250 | fid := fi.(*FileInfo).FileID.String() 251 | if fi.IsDir() { 252 | fid = fi.(*FileInfo).CategoryID.String() 253 | } 254 | 255 | srcPath = slashClean(srcPath) 256 | if srcPath == "/" || len(srcPath) == 0 { 257 | logrus.Warnf("invalid src_path: %s", srcPath) 258 | return nil 259 | } 260 | srcPath = strings.TrimRight(srcPath, "/") 261 | srcDir, srcFileName := path.Split(srcPath) 262 | 263 | dstPath = slashClean(dstPath) 264 | if dstPath == "/" || len(dstPath) == 0 { 265 | logrus.Warnf("invalid src_path: %s", srcPath) 266 | return nil 267 | } 268 | dstPath = strings.TrimRight(dstPath, "/") 269 | dstDir, dstFileName := path.Split(dstPath) 270 | 271 | dstDirFi, err := c.GetFile(dstDir) 272 | if err != nil { 273 | return err 274 | } 275 | if !dstDirFi.IsDir() { 276 | logrus.Errorf("dst dir not exists") 277 | return nil 278 | } 279 | 280 | if srcDir == dstDir { 281 | resp, err := APIRenameFile(c.HttpClient, fid, dstFileName) 282 | if err != nil { 283 | return err 284 | } 285 | if !resp.State { 286 | return fmt.Errorf("rename file fail, state is false") 287 | } 288 | c.flushDir(srcDir) 289 | } else { 290 | if srcFileName == dstFileName { 291 | resp, err := APIMoveFile(c.HttpClient, fid, dstDirFi.(*FileInfo).CategoryID.String()) 292 | if err != nil { 293 | return err 294 | } 295 | if !resp.State { 296 | return fmt.Errorf("move file fail, state is false") 297 | } 298 | c.flushDir(srcDir) 299 | c.flushDir(dstDir) 300 | } else { 301 | logrus.Errorf("invalid dst filename") 302 | return nil 303 | } 304 | } 305 | 306 | return nil 307 | } 308 | 309 | func (c *DriveClient) Proxy(w http.ResponseWriter, req *http.Request, targetURL string) { 310 | defer func() { 311 | if err := recover(); err != nil { 312 | if realErr, ok := err.(error); ok { 313 | if errors.Is(realErr, http.ErrAbortHandler) { 314 | logrus.WithError(realErr).Warnf("proxy abort error") 315 | return 316 | } 317 | } 318 | logrus.Errorf("panic: %v", err) 319 | w.WriteHeader(http.StatusInternalServerError) 320 | } 321 | }() 322 | 323 | u, _ := url.Parse(targetURL) 324 | req.URL = u 325 | req.Host = u.Host 326 | c.limiter.Wait(context.Background()) 327 | c.reserveProxy.ServeHTTP(w, req) 328 | } 329 | 330 | func (c *DriveClient) flushDir(dir string) { 331 | dir = slashClean(dir) 332 | dir = strings.TrimRight(dir, "/") 333 | if len(dir) == 0 { 334 | dir = "/" 335 | } 336 | c.cache.Remove(fmt.Sprintf("files:%s", dir)) 337 | } 338 | 339 | func slashClean(name string) string { 340 | if name == "" || name[0] != '/' { 341 | name = "/" + name 342 | } 343 | return path.Clean(name) 344 | } 345 | -------------------------------------------------------------------------------- /115/crypto.go: -------------------------------------------------------------------------------- 1 | package _115 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "encoding/base64" 9 | "encoding/pem" 10 | "io" 11 | "math/big" 12 | ) 13 | 14 | var ( 15 | xorKeySeed = []byte{ 16 | 0xf0, 0xe5, 0x69, 0xae, 0xbf, 0xdc, 0xbf, 0x8a, 17 | 0x1a, 0x45, 0xe8, 0xbe, 0x7d, 0xa6, 0x73, 0xb8, 18 | 0xde, 0x8f, 0xe7, 0xc4, 0x45, 0xda, 0x86, 0xc4, 19 | 0x9b, 0x64, 0x8b, 0x14, 0x6a, 0xb4, 0xf1, 0xaa, 20 | 0x38, 0x01, 0x35, 0x9e, 0x26, 0x69, 0x2c, 0x86, 21 | 0x00, 0x6b, 0x4f, 0xa5, 0x36, 0x34, 0x62, 0xa6, 22 | 0x2a, 0x96, 0x68, 0x18, 0xf2, 0x4a, 0xfd, 0xbd, 23 | 0x6b, 0x97, 0x8f, 0x4d, 0x8f, 0x89, 0x13, 0xb7, 24 | 0x6c, 0x8e, 0x93, 0xed, 0x0e, 0x0d, 0x48, 0x3e, 25 | 0xd7, 0x2f, 0x88, 0xd8, 0xfe, 0xfe, 0x7e, 0x86, 26 | 0x50, 0x95, 0x4f, 0xd1, 0xeb, 0x83, 0x26, 0x34, 27 | 0xdb, 0x66, 0x7b, 0x9c, 0x7e, 0x9d, 0x7a, 0x81, 28 | 0x32, 0xea, 0xb6, 0x33, 0xde, 0x3a, 0xa9, 0x59, 29 | 0x34, 0x66, 0x3b, 0xaa, 0xba, 0x81, 0x60, 0x48, 30 | 0xb9, 0xd5, 0x81, 0x9c, 0xf8, 0x6c, 0x84, 0x77, 31 | 0xff, 0x54, 0x78, 0x26, 0x5f, 0xbe, 0xe8, 0x1e, 32 | 0x36, 0x9f, 0x34, 0x80, 0x5c, 0x45, 0x2c, 0x9b, 33 | 0x76, 0xd5, 0x1b, 0x8f, 0xcc, 0xc3, 0xb8, 0xf5, 34 | } 35 | 36 | xorClientKey = []byte{ 37 | 0x78, 0x06, 0xad, 0x4c, 0x33, 0x86, 0x5d, 0x18, 38 | 0x4c, 0x01, 0x3f, 0x46, 39 | } 40 | 41 | rsaPublicKey = []byte("-----BEGIN PUBLIC KEY-----\n" + 42 | "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCGhpgMD1okxLnUMCDNLCJwP/P0\n" + 43 | "UHVlKQWLHPiPCbhgITZHcZim4mgxSWWb0SLDNZL9ta1HlErR6k02xrFyqtYzjDu2\n" + 44 | "rGInUC0BCZOsln0a7wDwyOA43i5NO8LsNory6fEKbx7aT3Ji8TZCDAfDMbhxvxOf\n" + 45 | "dPMBDjxP5X3zr7cWgwIDAQAB\n" + 46 | "-----END PUBLIC KEY-----") 47 | 48 | rsaServerKey *rsa.PublicKey 49 | ) 50 | 51 | type Key [16]byte 52 | 53 | func init() { 54 | block, _ := pem.Decode(rsaPublicKey) 55 | key, _ := x509.ParsePKIXPublicKey(block.Bytes) 56 | rsaServerKey = key.(*rsa.PublicKey) 57 | } 58 | 59 | func GenerateKey() Key { 60 | key := Key{} 61 | _, _ = io.ReadFull(rand.Reader, key[:]) 62 | return key 63 | } 64 | 65 | func Encode(input []byte, key Key) (output string) { 66 | buf := make([]byte, 16+len(input)) 67 | copy(buf, key[:]) 68 | copy(buf[16:], input) 69 | xorTransform(buf[16:], xorDeriveKey(key[:], 4)) 70 | reverseBytes(buf[16:]) 71 | xorTransform(buf[16:], xorClientKey) 72 | output = base64.StdEncoding.EncodeToString(rsaEncrypt(buf)) 73 | return 74 | } 75 | 76 | func Decode(input string, key Key) (output []byte, err error) { 77 | data, err := base64.StdEncoding.DecodeString(input) 78 | if err != nil { 79 | return 80 | } 81 | data = rsaDecrypt(data) 82 | output = make([]byte, len(data)-16) 83 | copy(output, data[16:]) 84 | xorTransform(output, xorDeriveKey(data[:16], 12)) 85 | reverseBytes(output) 86 | xorTransform(output, xorDeriveKey(key[:], 4)) 87 | return 88 | } 89 | 90 | func xorDeriveKey(seed []byte, size int) []byte { 91 | key := make([]byte, size) 92 | for i := 0; i < size; i++ { 93 | key[i] = (seed[i] + xorKeySeed[size*i]) & 0xff 94 | key[i] ^= xorKeySeed[size*(size-i-1)] 95 | } 96 | return key 97 | } 98 | 99 | func xorTransform(data []byte, key []byte) { 100 | dataSize, keySize := len(data), len(key) 101 | mod := dataSize % 4 102 | if mod > 0 { 103 | for i := 0; i < mod; i++ { 104 | data[i] ^= key[i%keySize] 105 | } 106 | } 107 | for i := mod; i < dataSize; i++ { 108 | data[i] ^= key[(i-mod)%keySize] 109 | } 110 | } 111 | 112 | func rsaEncrypt(input []byte) []byte { 113 | plainSize, blockSize := len(input), rsaServerKey.Size()-11 114 | buf := bytes.Buffer{} 115 | for offset := 0; offset < plainSize; offset += blockSize { 116 | sliceSize := blockSize 117 | if offset+sliceSize > plainSize { 118 | sliceSize = plainSize - offset 119 | } 120 | slice, _ := rsa.EncryptPKCS1v15( 121 | rand.Reader, rsaServerKey, input[offset:offset+sliceSize]) 122 | buf.Write(slice) 123 | } 124 | return buf.Bytes() 125 | } 126 | 127 | func rsaDecrypt(input []byte) []byte { 128 | output := make([]byte, 0) 129 | cipherSize, blockSize := len(input), rsaServerKey.Size() 130 | for offset := 0; offset < cipherSize; offset += blockSize { 131 | sliceSize := blockSize 132 | if offset+sliceSize > cipherSize { 133 | sliceSize = cipherSize - offset 134 | } 135 | 136 | n := big.NewInt(0).SetBytes(input[offset : offset+sliceSize]) 137 | m := big.NewInt(0).Exp(n, big.NewInt(int64(rsaServerKey.E)), rsaServerKey.N) 138 | b := m.Bytes() 139 | index := bytes.IndexByte(b, '\x00') 140 | if index < 0 { 141 | return nil 142 | } 143 | output = append(output, b[index+1:]...) 144 | } 145 | return output 146 | } 147 | 148 | func reverseBytes(data []byte) { 149 | for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 { 150 | data[i], data[j] = data[j], data[i] 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /115/types.go: -------------------------------------------------------------------------------- 1 | package _115 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | type DownloadURL struct { 9 | URL string `json:"url"` 10 | Client json.Number `json:"client"` 11 | Desc string `json:"desc"` 12 | OssID string `json:"oss_id"` 13 | } 14 | 15 | type DownloadInfo struct { 16 | FileName string `json:"file_name"` 17 | FileSize json.Number `json:"file_size"` 18 | PickCode string `json:"pick_code"` 19 | URL DownloadURL `json:"url"` 20 | } 21 | 22 | type DownloadData map[string]*DownloadInfo 23 | 24 | type APIBaseResp struct { 25 | State bool `json:"state"` 26 | Msg string `json:"msg"` 27 | Errno json.Number `json:"errno"` 28 | Data json.RawMessage `json:"data,omitempty"` 29 | } 30 | 31 | type FileInfo struct { 32 | AreaID json.Number `json:"aid"` 33 | CategoryID json.Number `json:"cid"` 34 | FileID json.Number `json:"fid"` 35 | ParentID json.Number `json:"pid"` 36 | 37 | Name string `json:"n"` 38 | Type string `json:"ico"` 39 | Size json.Number `json:"s"` 40 | Sha1 string `json:"sha"` 41 | PickCode string `json:"pc"` 42 | 43 | CreateTime json.Number `json:"tp"` 44 | UpdateTime json.Number `json:"te"` 45 | } 46 | 47 | type APIGetFileInfoResp struct { 48 | State bool `json:"state"` 49 | Code json.Number `json:"code"` 50 | Message string `json:"message"` 51 | Data []FileInfo `json:"data"` 52 | } 53 | 54 | type APIGetFilesResp struct { 55 | AreaID string `json:"aid"` 56 | CategoryID json.Number `json:"cid"` 57 | Count int64 `json:"count"` 58 | Cur int64 `json:"cur"` 59 | Data []FileInfo `json:"data"` 60 | DataSource string `json:"data_source"` 61 | ErrNo int64 `json:"errNo"` 62 | Error string `json:"error"` 63 | Limit int64 `json:"limit"` 64 | MaxSize int64 `json:"max_size"` 65 | MinSize int64 `json:"min_size"` 66 | Offset int64 `json:"offset"` 67 | Order string `json:"order"` 68 | PageSize int64 `json:"page_size"` 69 | Path []FileInfo `json:"path"` 70 | State bool `json:"state"` 71 | Suffix string `json:"suffix"` 72 | } 73 | 74 | type APIGetDirIDResp struct { 75 | ErrNo json.Number `json:"errno"` 76 | Error string `json:"error"` 77 | CategoryID json.Number `json:"id"` 78 | IsPrivate json.Number `json:"is_private"` 79 | State bool `json:"state"` 80 | } 81 | 82 | type APIDeleteFileResp struct { 83 | // ErrNo json.Number `json:"errno"` 84 | Error string `json:"error"` 85 | State bool `json:"state"` 86 | } 87 | 88 | type APIAddDirResp struct { 89 | // ErrNo json.Number `json:"errno"` 90 | Error string `json:"error"` 91 | State bool `json:"state"` 92 | } 93 | 94 | type APICopyFileResp struct { 95 | // ErrNo json.Number `json:"errno"` 96 | Error string `json:"error"` 97 | State bool `json:"state"` 98 | } 99 | 100 | type APIMoveFileResp struct { 101 | // ErrNo json.Number `json:"errno"` 102 | Error string `json:"error"` 103 | State bool `json:"state"` 104 | } 105 | 106 | type APIRenameFileResp struct { 107 | // ErrNo json.Number `json:"errno"` 108 | Error string `json:"error"` 109 | State bool `json:"state"` 110 | } 111 | 112 | type APILoginCheckResp struct { 113 | ErrNo json.Number `json:"errno"` 114 | Error string `json:"error"` 115 | Data struct { 116 | Expire json.Number `json:"expire"` 117 | UserID json.Number `json:"user_id"` 118 | Link string `json:"link"` 119 | } `json:"data"` 120 | } 121 | 122 | func (f *FileInfo) GetName() string { 123 | return f.Name 124 | } 125 | 126 | func (f *FileInfo) GetSize() int64 { 127 | size, _ := f.Size.Int64() 128 | return size 129 | } 130 | 131 | func (f *FileInfo) GetUpdateTime() time.Time { 132 | updateTime, _ := f.UpdateTime.Int64() 133 | return time.Unix(updateTime, 0).UTC() 134 | } 135 | 136 | func (f *FileInfo) GetCreateTime() time.Time { 137 | updateTime, _ := f.UpdateTime.Int64() 138 | return time.Unix(updateTime, 0).UTC() 139 | } 140 | 141 | func (f *FileInfo) IsDir() bool { 142 | fid, _ := f.FileID.Int64() 143 | return fid == 0 144 | } 145 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | COPY 115drive-webdav /usr/bin/115drive-webdav 4 | 5 | ENTRYPOINT ["/usr/bin/115drive-webdav"] 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 115drive-webdav 2 | 3 | [![GitHub Actions](https://img.shields.io/github/actions/workflow/status/gaoyb7/115drive-webdav/ci.yml?branch=main)](https://github.com/gaoyb7/115drive-webdav/actions) 4 | [![Release](https://img.shields.io/github/v/release/gaoyb7/115drive-webdav?display_name=tag)](https://github.com/gaoyb7/115drive-webdav/releases) 5 | [![Downloads](https://img.shields.io/github/downloads/gaoyb7/115drive-webdav/total)](https://github.com/gaoyb7/115drive-webdav/releases) 6 | [![Docker Image](https://img.shields.io/docker/pulls/gaoyb7/115drive-webdav)](https://hub.docker.com/r/gaoyb7/115drive-webdav) 7 | 8 | 115 网盘 WebDav 服务,可配合支持 WebDAV 协议的客户端 App 食用,如 [Infuse](https://firecore.com/infuse)、[nPlayer](https://nplayer.com) 9 | 10 | 新项目 rclone 改版,对比 115drive-webdav 功能更强大,支持 WebDav 服务,本地磁盘挂载,文件批量下载到本地等功能。https://github.com/gaoyb7/rclone-release 11 | 12 | ## 下载 13 | https://github.com/gaoyb7/115drive-webdav/releases 14 | 15 | ## 运行 16 | 需要获取 115 网盘 Cookie 信息,包括 UID、CID、SEID、KID,网页版 Cookie 时效较短,建议抓包 App 请求获取 Cookie,iOS 系统可使用 [Stream](https://apps.apple.com/cn/app/stream/id1312141691) 抓包,安卓系统使用抓包精灵 17 | ```bash 18 | # xxxx 替换为对应的 UID、CID、SEID、KID 值 19 | ./115drive-webdav --host=0.0.0.0 --port=8080 --user=user --pwd=123456 --uid=xxxxxx --cid=xxxxxxx --seid=xxxxx --kid=xxxxxx 20 | ``` 21 | 服务启动成功后,用支持 WebDav 协议的客户端连接即可,不支持浏览器直接打开 22 | 23 | ## Docker 运行 24 | ```bash 25 | # 通过命令参数获取配置 26 | docker run -d \ 27 | -p 8081:8081 \ 28 | --restart unless-stopped \ 29 | gaoyb7/115drive-webdav:latest \ 30 | --host=0.0.0.0 --port=8081 \ 31 | --user=user --pwd=123456 \ 32 | --uid=xxxxxx \ 33 | --cid=xxxxxx \ 34 | --seid=xxxxxx \ 35 | --kid=xxxxxx 36 | 37 | # 通过配置文件获取配置 38 | # /path/to/your/config 替换为实际配置文件地址 39 | docker run -d \ 40 | -p 8081:8081 \ 41 | -v /path/to/your/config:/etc/115drive-webdav.json \ 42 | --restart unless-stopped \ 43 | gaoyb7/115drive-webdav \ 44 | --config /etc/115drive-webdav.json 45 | ``` 46 | 47 | ## 参数说明 48 | ```bash 49 | --host 50 | 服务监听地址,默认 0.0.0.0 51 | --port 52 | 服务监听端口,默认 8080 53 | --user 54 | WebDav 账户用户名,默认 user 55 | --pwd 56 | WebDav 账户密码,默认 123456 57 | --uid 58 | 115 网盘 Cookie,UID 59 | --cid 60 | 115 网盘 Cookie,CID 61 | --seid 62 | 115 网盘 Cookie,SEID 63 | --kid 64 | 115 网盘 Cookie,KID 65 | --config 66 | 从文件中读取配置,参考 config.json.example 67 | ``` 68 | 69 | ## 功能支持 70 | 71 | - [x] 文件/文件夹查看 72 | - [x] 文件下载 73 | - [x] WebDav 权限校验 74 | - [x] WebDav 在线视频播放 75 | - [ ] 文件上传 76 | - [x] 文件重命名 77 | - [x] 文件删除 78 | - [x] 文件移动 79 | 80 | ## App Cookie 获取方法 81 | ### iOS 82 | * 使用 Stream 抓包,参考 https://cloud.tencent.com/developer/article/1670286 83 | 84 | ### Android 85 | * 方法一:可使用抓包精灵,类似 Stream。参考 https://play.google.com/store/apps/details?id=com.minhui.networkcapture&hl=zh&gl=US 86 | * 方法二:使用 Charles 抓包,参考 https://myoule.zhipin.com/articles/c27b2972802dc15fqxB72Ny9Eg~~.html 87 | -------------------------------------------------------------------------------- /common/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "io/ioutil" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | type config struct { 12 | Uid string `json:"uid"` 13 | Cid string `json:"cid"` 14 | Seid string `json:"seid"` 15 | Kid string `json:"kid"` 16 | Host string `json:"host"` 17 | Port int `json:"port"` 18 | User string `json:"user"` 19 | Password string `json:"pwd"` 20 | } 21 | 22 | var ( 23 | Config config 24 | ) 25 | 26 | var ( 27 | cliConfig = flag.String("config", "", "config file") 28 | cliUid = flag.String("uid", "", "115 cookie uid") 29 | cliCid = flag.String("cid", "", "115 cookie cid") 30 | cliSeid = flag.String("seid", "", "115 cookie seid") 31 | cliKid = flag.String("kid", "", "115 cookie kid") 32 | cliHost = flag.String("host", "0.0.0.0", "webdav server host") 33 | cliPort = flag.Int("port", 8080, "webdav server port") 34 | cliUser = flag.String("user", "user", "webdav auth username") 35 | cliPassword = flag.String("pwd", "123456", "webdav auth password") 36 | ) 37 | 38 | func init() { 39 | flag.Parse() 40 | if len(*cliConfig) > 0 { 41 | load(*cliConfig) 42 | return 43 | } 44 | 45 | Config.Uid = *cliUid 46 | Config.Cid = *cliCid 47 | Config.Seid = *cliSeid 48 | Config.Kid = *cliKid 49 | Config.Host = *cliHost 50 | Config.Port = *cliPort 51 | Config.User = *cliUser 52 | Config.Password = *cliPassword 53 | } 54 | 55 | func load(filename string) { 56 | data, err := ioutil.ReadFile(filename) 57 | if err != nil { 58 | logrus.WithError(err).Panicf("call ioutil.ReadFile fail, filename: %v", filename) 59 | } 60 | 61 | err = json.Unmarshal(data, &Config) 62 | if err != nil { 63 | logrus.WithError(err).Panicf("call json.Unmarshal fail, filename: %v", filename) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /common/drive/interface.go: -------------------------------------------------------------------------------- 1 | package drive 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | type File interface { 9 | GetName() string 10 | GetSize() int64 11 | GetUpdateTime() time.Time 12 | GetCreateTime() time.Time 13 | IsDir() bool 14 | } 15 | 16 | type DriveClient interface { 17 | GetFiles(dir string) ([]File, error) 18 | GetFile(filePath string) (File, error) 19 | RemoveFile(filePath string) error 20 | MoveFile(srcPath string, dstPath string) error 21 | MakeDir(dir string) error 22 | ServeContent(w http.ResponseWriter, req *http.Request, fi File) 23 | } 24 | -------------------------------------------------------------------------------- /common/errors.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrNotFound = errors.New("not found") 7 | ) 8 | -------------------------------------------------------------------------------- /config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "uid": "", 3 | "cid": "", 4 | "seid": "", 5 | "kid": "", 6 | "host": "0.0.0.0", 7 | "port": 8081, 8 | "user": "user", 9 | "pwd": "123456" 10 | } 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gaoyb7/115drive-webdav 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/bluele/gcache v0.0.2 7 | github.com/gin-gonic/gin v1.8.1 8 | github.com/go-playground/validator/v10 v10.11.0 // indirect 9 | github.com/go-resty/resty/v2 v2.7.0 10 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 11 | github.com/sirupsen/logrus v1.8.1 12 | github.com/stretchr/testify v1.7.2 // indirect 13 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect 14 | golang.org/x/sys v0.0.0-20220702020025-31831981b65f // indirect 15 | golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= 2 | github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 8 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 9 | github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= 10 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= 11 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 12 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 13 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 14 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 15 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 16 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 17 | github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= 18 | github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= 19 | github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= 20 | github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= 21 | github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= 22 | github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= 23 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 24 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 25 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 26 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 27 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 28 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 29 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 30 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 31 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 32 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 33 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 34 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 35 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 36 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 37 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 38 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 39 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 40 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 41 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 42 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 45 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 46 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 47 | github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= 48 | github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= 49 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 50 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 51 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 52 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 53 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 54 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 55 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 56 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 57 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 58 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 59 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 60 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 61 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 62 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 63 | github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= 64 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 65 | github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= 66 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= 67 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= 68 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 69 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 70 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 71 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= 72 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 73 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 74 | golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 75 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= 76 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 77 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 78 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 79 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 80 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 81 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 82 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 83 | golang.org/x/sys v0.0.0-20220702020025-31831981b65f h1:xdsejrW/0Wf2diT5CPp3XmKUNbr7Xvw8kYilQ+6qjRY= 84 | golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 85 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 86 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 87 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 88 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 89 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 90 | golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= 91 | golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 92 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 93 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 94 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 95 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 96 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 97 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 98 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 99 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 100 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 101 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 102 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 103 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 104 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 105 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 106 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 107 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 108 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 109 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | _115 "github.com/gaoyb7/115drive-webdav/115" 8 | "github.com/gaoyb7/115drive-webdav/common/config" 9 | "github.com/gaoyb7/115drive-webdav/webdav" 10 | "github.com/gin-gonic/gin" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | var ( 15 | cfg = config.Config 16 | ) 17 | 18 | func main() { 19 | logrus.SetReportCaller(true) 20 | webdavHandler := webdav.Handler{ 21 | DriveClient: _115.MustNew115DriveClient(cfg.Uid, cfg.Cid, cfg.Seid, cfg.Kid), 22 | LockSystem: webdav.NewMemLS(), 23 | Logger: func(req *http.Request, err error) { 24 | if err != nil { 25 | logrus.WithField("method", req.Method).WithField("path", req.URL.Path).Errorf("err: %v", err) 26 | } 27 | }, 28 | } 29 | webdavHandleFunc := func(c *gin.Context) { 30 | webdavHandler.ServeHTTP(c.Writer, c.Request) 31 | } 32 | 33 | gin.SetMode(gin.ReleaseMode) 34 | r := gin.Default() 35 | dav := r.Group("", gin.BasicAuth(gin.Accounts{ 36 | cfg.User: cfg.Password, 37 | })) 38 | dav.Any("/*path", webdavHandleFunc) 39 | dav.Handle("PROPFIND", "/*path", webdavHandleFunc) 40 | dav.Handle("MKCOL", "/*path", webdavHandleFunc) 41 | dav.Handle("LOCK", "/*path", webdavHandleFunc) 42 | dav.Handle("UNLOCK", "/*path", webdavHandleFunc) 43 | dav.Handle("PROPPATCH", "/*path", webdavHandleFunc) 44 | dav.Handle("COPY", "/*path", webdavHandleFunc) 45 | dav.Handle("MOVE", "/*path", webdavHandleFunc) 46 | 47 | if err := r.Run(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)); err != nil { 48 | logrus.Panic(err) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /systemd.service.example: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=115drive-webdav 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=115drive-webdav --config=/etc/115drive-webdav.json 8 | KillMode=process 9 | Restart=on-failure 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /webdav/file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package webdav 6 | 7 | import ( 8 | "context" 9 | "path" 10 | "path/filepath" 11 | 12 | "github.com/gaoyb7/115drive-webdav/common/drive" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | type WalkFunc func(path string, info drive.File, err error) error 17 | 18 | func slashClean(name string) string { 19 | if name == "" || name[0] != '/' { 20 | name = "/" + name 21 | } 22 | return path.Clean(name) 23 | } 24 | 25 | // walkFS traverses filesystem fs starting at name up to depth levels. 26 | // 27 | // Allowed values for depth are 0, 1 or infiniteDepth. For each visited node, 28 | // walkFS calls walkFn. If a visited file system node is a directory and 29 | // walkFn returns filepath.SkipDir, walkFS will skip traversal of this node. 30 | func walkFS(ctx context.Context, depth int, name string, fs drive.DriveClient, fi drive.File, walkFn WalkFunc) error { 31 | // This implementation is based on Walk's code in the standard path/filepath package. 32 | err := walkFn(name, fi, nil) 33 | if err != nil { 34 | if fi.IsDir() && err == filepath.SkipDir { 35 | return nil 36 | } 37 | return err 38 | } 39 | if !fi.IsDir() || depth == 0 { 40 | return nil 41 | } 42 | if depth == 1 { 43 | depth = 0 44 | } 45 | 46 | // Read directory names. 47 | files, err := fs.GetFiles(name) 48 | if err != nil { 49 | logrus.WithError(err).Errorf("call client.GetFiles fail, name: %s", name) 50 | return walkFn(name, fi, err) 51 | } 52 | 53 | for _, file := range files { 54 | filename := path.Join(name, file.GetName()) 55 | err := walkFS(ctx, depth, filename, fs, file, walkFn) 56 | if err != nil { 57 | if !file.IsDir() || err != filepath.SkipDir { 58 | return err 59 | } 60 | } 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /webdav/if.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package webdav 6 | 7 | // The If header is covered by Section 10.4. 8 | // http://www.webdav.org/specs/rfc4918.html#HEADER_If 9 | 10 | import ( 11 | "strings" 12 | ) 13 | 14 | // ifHeader is a disjunction (OR) of ifLists. 15 | type ifHeader struct { 16 | lists []ifList 17 | } 18 | 19 | // ifList is a conjunction (AND) of Conditions, and an optional resource tag. 20 | type ifList struct { 21 | resourceTag string 22 | conditions []Condition 23 | } 24 | 25 | // parseIfHeader parses the "If: foo bar" HTTP header. The httpHeader string 26 | // should omit the "If:" prefix and have any "\r\n"s collapsed to a " ", as is 27 | // returned by req.Header.Get("If") for a http.Request req. 28 | func parseIfHeader(httpHeader string) (h ifHeader, ok bool) { 29 | s := strings.TrimSpace(httpHeader) 30 | switch tokenType, _, _ := lex(s); tokenType { 31 | case '(': 32 | return parseNoTagLists(s) 33 | case angleTokenType: 34 | return parseTaggedLists(s) 35 | default: 36 | return ifHeader{}, false 37 | } 38 | } 39 | 40 | func parseNoTagLists(s string) (h ifHeader, ok bool) { 41 | for { 42 | l, remaining, ok := parseList(s) 43 | if !ok { 44 | return ifHeader{}, false 45 | } 46 | h.lists = append(h.lists, l) 47 | if remaining == "" { 48 | return h, true 49 | } 50 | s = remaining 51 | } 52 | } 53 | 54 | func parseTaggedLists(s string) (h ifHeader, ok bool) { 55 | resourceTag, n := "", 0 56 | for first := true; ; first = false { 57 | tokenType, tokenStr, remaining := lex(s) 58 | switch tokenType { 59 | case angleTokenType: 60 | if !first && n == 0 { 61 | return ifHeader{}, false 62 | } 63 | resourceTag, n = tokenStr, 0 64 | s = remaining 65 | case '(': 66 | n++ 67 | l, remaining, ok := parseList(s) 68 | if !ok { 69 | return ifHeader{}, false 70 | } 71 | l.resourceTag = resourceTag 72 | h.lists = append(h.lists, l) 73 | if remaining == "" { 74 | return h, true 75 | } 76 | s = remaining 77 | default: 78 | return ifHeader{}, false 79 | } 80 | } 81 | } 82 | 83 | func parseList(s string) (l ifList, remaining string, ok bool) { 84 | tokenType, _, s := lex(s) 85 | if tokenType != '(' { 86 | return ifList{}, "", false 87 | } 88 | for { 89 | tokenType, _, remaining = lex(s) 90 | if tokenType == ')' { 91 | if len(l.conditions) == 0 { 92 | return ifList{}, "", false 93 | } 94 | return l, remaining, true 95 | } 96 | c, remaining, ok := parseCondition(s) 97 | if !ok { 98 | return ifList{}, "", false 99 | } 100 | l.conditions = append(l.conditions, c) 101 | s = remaining 102 | } 103 | } 104 | 105 | func parseCondition(s string) (c Condition, remaining string, ok bool) { 106 | tokenType, tokenStr, s := lex(s) 107 | if tokenType == notTokenType { 108 | c.Not = true 109 | tokenType, tokenStr, s = lex(s) 110 | } 111 | switch tokenType { 112 | case strTokenType, angleTokenType: 113 | c.Token = tokenStr 114 | case squareTokenType: 115 | c.ETag = tokenStr 116 | default: 117 | return Condition{}, "", false 118 | } 119 | return c, s, true 120 | } 121 | 122 | // Single-rune tokens like '(' or ')' have a token type equal to their rune. 123 | // All other tokens have a negative token type. 124 | const ( 125 | errTokenType = rune(-1) 126 | eofTokenType = rune(-2) 127 | strTokenType = rune(-3) 128 | notTokenType = rune(-4) 129 | angleTokenType = rune(-5) 130 | squareTokenType = rune(-6) 131 | ) 132 | 133 | func lex(s string) (tokenType rune, tokenStr string, remaining string) { 134 | // The net/textproto Reader that parses the HTTP header will collapse 135 | // Linear White Space that spans multiple "\r\n" lines to a single " ", 136 | // so we don't need to look for '\r' or '\n'. 137 | for len(s) > 0 && (s[0] == '\t' || s[0] == ' ') { 138 | s = s[1:] 139 | } 140 | if len(s) == 0 { 141 | return eofTokenType, "", "" 142 | } 143 | i := 0 144 | loop: 145 | for ; i < len(s); i++ { 146 | switch s[i] { 147 | case '\t', ' ', '(', ')', '<', '>', '[', ']': 148 | break loop 149 | } 150 | } 151 | 152 | if i != 0 { 153 | tokenStr, remaining = s[:i], s[i:] 154 | if tokenStr == "Not" { 155 | return notTokenType, "", remaining 156 | } 157 | return strTokenType, tokenStr, remaining 158 | } 159 | 160 | j := 0 161 | switch s[0] { 162 | case '<': 163 | j, tokenType = strings.IndexByte(s, '>'), angleTokenType 164 | case '[': 165 | j, tokenType = strings.IndexByte(s, ']'), squareTokenType 166 | default: 167 | return rune(s[0]), "", s[1:] 168 | } 169 | if j < 0 { 170 | return errTokenType, "", "" 171 | } 172 | return tokenType, s[1:j], s[j+1:] 173 | } 174 | -------------------------------------------------------------------------------- /webdav/internal/xml/marshal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xml 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "encoding" 11 | "fmt" 12 | "io" 13 | "reflect" 14 | "strconv" 15 | "strings" 16 | ) 17 | 18 | const ( 19 | // A generic XML header suitable for use with the output of Marshal. 20 | // This is not automatically added to any output of this package, 21 | // it is provided as a convenience. 22 | Header = `` + "\n" 23 | ) 24 | 25 | // Marshal returns the XML encoding of v. 26 | // 27 | // Marshal handles an array or slice by marshalling each of the elements. 28 | // Marshal handles a pointer by marshalling the value it points at or, if the 29 | // pointer is nil, by writing nothing. Marshal handles an interface value by 30 | // marshalling the value it contains or, if the interface value is nil, by 31 | // writing nothing. Marshal handles all other data by writing one or more XML 32 | // elements containing the data. 33 | // 34 | // The name for the XML elements is taken from, in order of preference: 35 | // - the tag on the XMLName field, if the data is a struct 36 | // - the value of the XMLName field of type xml.Name 37 | // - the tag of the struct field used to obtain the data 38 | // - the name of the struct field used to obtain the data 39 | // - the name of the marshalled type 40 | // 41 | // The XML element for a struct contains marshalled elements for each of the 42 | // exported fields of the struct, with these exceptions: 43 | // - the XMLName field, described above, is omitted. 44 | // - a field with tag "-" is omitted. 45 | // - a field with tag "name,attr" becomes an attribute with 46 | // the given name in the XML element. 47 | // - a field with tag ",attr" becomes an attribute with the 48 | // field name in the XML element. 49 | // - a field with tag ",chardata" is written as character data, 50 | // not as an XML element. 51 | // - a field with tag ",innerxml" is written verbatim, not subject 52 | // to the usual marshalling procedure. 53 | // - a field with tag ",comment" is written as an XML comment, not 54 | // subject to the usual marshalling procedure. It must not contain 55 | // the "--" string within it. 56 | // - a field with a tag including the "omitempty" option is omitted 57 | // if the field value is empty. The empty values are false, 0, any 58 | // nil pointer or interface value, and any array, slice, map, or 59 | // string of length zero. 60 | // - an anonymous struct field is handled as if the fields of its 61 | // value were part of the outer struct. 62 | // 63 | // If a field uses a tag "a>b>c", then the element c will be nested inside 64 | // parent elements a and b. Fields that appear next to each other that name 65 | // the same parent will be enclosed in one XML element. 66 | // 67 | // See MarshalIndent for an example. 68 | // 69 | // Marshal will return an error if asked to marshal a channel, function, or map. 70 | func Marshal(v interface{}) ([]byte, error) { 71 | var b bytes.Buffer 72 | if err := NewEncoder(&b).Encode(v); err != nil { 73 | return nil, err 74 | } 75 | return b.Bytes(), nil 76 | } 77 | 78 | // Marshaler is the interface implemented by objects that can marshal 79 | // themselves into valid XML elements. 80 | // 81 | // MarshalXML encodes the receiver as zero or more XML elements. 82 | // By convention, arrays or slices are typically encoded as a sequence 83 | // of elements, one per entry. 84 | // Using start as the element tag is not required, but doing so 85 | // will enable Unmarshal to match the XML elements to the correct 86 | // struct field. 87 | // One common implementation strategy is to construct a separate 88 | // value with a layout corresponding to the desired XML and then 89 | // to encode it using e.EncodeElement. 90 | // Another common strategy is to use repeated calls to e.EncodeToken 91 | // to generate the XML output one token at a time. 92 | // The sequence of encoded tokens must make up zero or more valid 93 | // XML elements. 94 | type Marshaler interface { 95 | MarshalXML(e *Encoder, start StartElement) error 96 | } 97 | 98 | // MarshalerAttr is the interface implemented by objects that can marshal 99 | // themselves into valid XML attributes. 100 | // 101 | // MarshalXMLAttr returns an XML attribute with the encoded value of the receiver. 102 | // Using name as the attribute name is not required, but doing so 103 | // will enable Unmarshal to match the attribute to the correct 104 | // struct field. 105 | // If MarshalXMLAttr returns the zero attribute Attr{}, no attribute 106 | // will be generated in the output. 107 | // MarshalXMLAttr is used only for struct fields with the 108 | // "attr" option in the field tag. 109 | type MarshalerAttr interface { 110 | MarshalXMLAttr(name Name) (Attr, error) 111 | } 112 | 113 | // MarshalIndent works like Marshal, but each XML element begins on a new 114 | // indented line that starts with prefix and is followed by one or more 115 | // copies of indent according to the nesting depth. 116 | func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { 117 | var b bytes.Buffer 118 | enc := NewEncoder(&b) 119 | enc.Indent(prefix, indent) 120 | if err := enc.Encode(v); err != nil { 121 | return nil, err 122 | } 123 | return b.Bytes(), nil 124 | } 125 | 126 | // An Encoder writes XML data to an output stream. 127 | type Encoder struct { 128 | p printer 129 | } 130 | 131 | // NewEncoder returns a new encoder that writes to w. 132 | func NewEncoder(w io.Writer) *Encoder { 133 | e := &Encoder{printer{Writer: bufio.NewWriter(w)}} 134 | e.p.encoder = e 135 | return e 136 | } 137 | 138 | // Indent sets the encoder to generate XML in which each element 139 | // begins on a new indented line that starts with prefix and is followed by 140 | // one or more copies of indent according to the nesting depth. 141 | func (enc *Encoder) Indent(prefix, indent string) { 142 | enc.p.prefix = prefix 143 | enc.p.indent = indent 144 | } 145 | 146 | // Encode writes the XML encoding of v to the stream. 147 | // 148 | // See the documentation for Marshal for details about the conversion 149 | // of Go values to XML. 150 | // 151 | // Encode calls Flush before returning. 152 | func (enc *Encoder) Encode(v interface{}) error { 153 | err := enc.p.marshalValue(reflect.ValueOf(v), nil, nil) 154 | if err != nil { 155 | return err 156 | } 157 | return enc.p.Flush() 158 | } 159 | 160 | // EncodeElement writes the XML encoding of v to the stream, 161 | // using start as the outermost tag in the encoding. 162 | // 163 | // See the documentation for Marshal for details about the conversion 164 | // of Go values to XML. 165 | // 166 | // EncodeElement calls Flush before returning. 167 | func (enc *Encoder) EncodeElement(v interface{}, start StartElement) error { 168 | err := enc.p.marshalValue(reflect.ValueOf(v), nil, &start) 169 | if err != nil { 170 | return err 171 | } 172 | return enc.p.Flush() 173 | } 174 | 175 | var ( 176 | begComment = []byte("") 178 | endProcInst = []byte("?>") 179 | endDirective = []byte(">") 180 | ) 181 | 182 | // EncodeToken writes the given XML token to the stream. 183 | // It returns an error if StartElement and EndElement tokens are not 184 | // properly matched. 185 | // 186 | // EncodeToken does not call Flush, because usually it is part of a 187 | // larger operation such as Encode or EncodeElement (or a custom 188 | // Marshaler's MarshalXML invoked during those), and those will call 189 | // Flush when finished. Callers that create an Encoder and then invoke 190 | // EncodeToken directly, without using Encode or EncodeElement, need to 191 | // call Flush when finished to ensure that the XML is written to the 192 | // underlying writer. 193 | // 194 | // EncodeToken allows writing a ProcInst with Target set to "xml" only 195 | // as the first token in the stream. 196 | // 197 | // When encoding a StartElement holding an XML namespace prefix 198 | // declaration for a prefix that is not already declared, contained 199 | // elements (including the StartElement itself) will use the declared 200 | // prefix when encoding names with matching namespace URIs. 201 | func (enc *Encoder) EncodeToken(t Token) error { 202 | 203 | p := &enc.p 204 | switch t := t.(type) { 205 | case StartElement: 206 | if err := p.writeStart(&t); err != nil { 207 | return err 208 | } 209 | case EndElement: 210 | if err := p.writeEnd(t.Name); err != nil { 211 | return err 212 | } 213 | case CharData: 214 | escapeText(p, t, false) 215 | case Comment: 216 | if bytes.Contains(t, endComment) { 217 | return fmt.Errorf("xml: EncodeToken of Comment containing --> marker") 218 | } 219 | p.WriteString("") 222 | return p.cachedWriteError() 223 | case ProcInst: 224 | // First token to be encoded which is also a ProcInst with target of xml 225 | // is the xml declaration. The only ProcInst where target of xml is allowed. 226 | if t.Target == "xml" && p.Buffered() != 0 { 227 | return fmt.Errorf("xml: EncodeToken of ProcInst xml target only valid for xml declaration, first token encoded") 228 | } 229 | if !isNameString(t.Target) { 230 | return fmt.Errorf("xml: EncodeToken of ProcInst with invalid Target") 231 | } 232 | if bytes.Contains(t.Inst, endProcInst) { 233 | return fmt.Errorf("xml: EncodeToken of ProcInst containing ?> marker") 234 | } 235 | p.WriteString(" 0 { 238 | p.WriteByte(' ') 239 | p.Write(t.Inst) 240 | } 241 | p.WriteString("?>") 242 | case Directive: 243 | if !isValidDirective(t) { 244 | return fmt.Errorf("xml: EncodeToken of Directive containing wrong < or > markers") 245 | } 246 | p.WriteString("") 249 | default: 250 | return fmt.Errorf("xml: EncodeToken of invalid token type") 251 | 252 | } 253 | return p.cachedWriteError() 254 | } 255 | 256 | // isValidDirective reports whether dir is a valid directive text, 257 | // meaning angle brackets are matched, ignoring comments and strings. 258 | func isValidDirective(dir Directive) bool { 259 | var ( 260 | depth int 261 | inquote uint8 262 | incomment bool 263 | ) 264 | for i, c := range dir { 265 | switch { 266 | case incomment: 267 | if c == '>' { 268 | if n := 1 + i - len(endComment); n >= 0 && bytes.Equal(dir[n:i+1], endComment) { 269 | incomment = false 270 | } 271 | } 272 | // Just ignore anything in comment 273 | case inquote != 0: 274 | if c == inquote { 275 | inquote = 0 276 | } 277 | // Just ignore anything within quotes 278 | case c == '\'' || c == '"': 279 | inquote = c 280 | case c == '<': 281 | if i+len(begComment) < len(dir) && bytes.Equal(dir[i:i+len(begComment)], begComment) { 282 | incomment = true 283 | } else { 284 | depth++ 285 | } 286 | case c == '>': 287 | if depth == 0 { 288 | return false 289 | } 290 | depth-- 291 | } 292 | } 293 | return depth == 0 && inquote == 0 && !incomment 294 | } 295 | 296 | // Flush flushes any buffered XML to the underlying writer. 297 | // See the EncodeToken documentation for details about when it is necessary. 298 | func (enc *Encoder) Flush() error { 299 | return enc.p.Flush() 300 | } 301 | 302 | type printer struct { 303 | *bufio.Writer 304 | encoder *Encoder 305 | seq int 306 | indent string 307 | prefix string 308 | depth int 309 | indentedIn bool 310 | putNewline bool 311 | defaultNS string 312 | attrNS map[string]string // map prefix -> name space 313 | attrPrefix map[string]string // map name space -> prefix 314 | prefixes []printerPrefix 315 | tags []Name 316 | } 317 | 318 | // printerPrefix holds a namespace undo record. 319 | // When an element is popped, the prefix record 320 | // is set back to the recorded URL. The empty 321 | // prefix records the URL for the default name space. 322 | // 323 | // The start of an element is recorded with an element 324 | // that has mark=true. 325 | type printerPrefix struct { 326 | prefix string 327 | url string 328 | mark bool 329 | } 330 | 331 | func (p *printer) prefixForNS(url string, isAttr bool) string { 332 | // The "http://www.w3.org/XML/1998/namespace" name space is predefined as "xml" 333 | // and must be referred to that way. 334 | // (The "http://www.w3.org/2000/xmlns/" name space is also predefined as "xmlns", 335 | // but users should not be trying to use that one directly - that's our job.) 336 | if url == xmlURL { 337 | return "xml" 338 | } 339 | if !isAttr && url == p.defaultNS { 340 | // We can use the default name space. 341 | return "" 342 | } 343 | return p.attrPrefix[url] 344 | } 345 | 346 | // defineNS pushes any namespace definition found in the given attribute. 347 | // If ignoreNonEmptyDefault is true, an xmlns="nonempty" 348 | // attribute will be ignored. 349 | func (p *printer) defineNS(attr Attr, ignoreNonEmptyDefault bool) error { 350 | var prefix string 351 | if attr.Name.Local == "xmlns" { 352 | if attr.Name.Space != "" && attr.Name.Space != "xml" && attr.Name.Space != xmlURL { 353 | return fmt.Errorf("xml: cannot redefine xmlns attribute prefix") 354 | } 355 | } else if attr.Name.Space == "xmlns" && attr.Name.Local != "" { 356 | prefix = attr.Name.Local 357 | if attr.Value == "" { 358 | // Technically, an empty XML namespace is allowed for an attribute. 359 | // From http://www.w3.org/TR/xml-names11/#scoping-defaulting: 360 | // 361 | // The attribute value in a namespace declaration for a prefix may be 362 | // empty. This has the effect, within the scope of the declaration, of removing 363 | // any association of the prefix with a namespace name. 364 | // 365 | // However our namespace prefixes here are used only as hints. There's 366 | // no need to respect the removal of a namespace prefix, so we ignore it. 367 | return nil 368 | } 369 | } else { 370 | // Ignore: it's not a namespace definition 371 | return nil 372 | } 373 | if prefix == "" { 374 | if attr.Value == p.defaultNS { 375 | // No need for redefinition. 376 | return nil 377 | } 378 | if attr.Value != "" && ignoreNonEmptyDefault { 379 | // We have an xmlns="..." value but 380 | // it can't define a name space in this context, 381 | // probably because the element has an empty 382 | // name space. In this case, we just ignore 383 | // the name space declaration. 384 | return nil 385 | } 386 | } else if _, ok := p.attrPrefix[attr.Value]; ok { 387 | // There's already a prefix for the given name space, 388 | // so use that. This prevents us from 389 | // having two prefixes for the same name space 390 | // so attrNS and attrPrefix can remain bijective. 391 | return nil 392 | } 393 | p.pushPrefix(prefix, attr.Value) 394 | return nil 395 | } 396 | 397 | // createNSPrefix creates a name space prefix attribute 398 | // to use for the given name space, defining a new prefix 399 | // if necessary. 400 | // If isAttr is true, the prefix is to be created for an attribute 401 | // prefix, which means that the default name space cannot 402 | // be used. 403 | func (p *printer) createNSPrefix(url string, isAttr bool) { 404 | if _, ok := p.attrPrefix[url]; ok { 405 | // We already have a prefix for the given URL. 406 | return 407 | } 408 | switch { 409 | case !isAttr && url == p.defaultNS: 410 | // We can use the default name space. 411 | return 412 | case url == "": 413 | // The only way we can encode names in the empty 414 | // name space is by using the default name space, 415 | // so we must use that. 416 | if p.defaultNS != "" { 417 | // The default namespace is non-empty, so we 418 | // need to set it to empty. 419 | p.pushPrefix("", "") 420 | } 421 | return 422 | case url == xmlURL: 423 | return 424 | } 425 | // TODO If the URL is an existing prefix, we could 426 | // use it as is. That would enable the 427 | // marshaling of elements that had been unmarshaled 428 | // and with a name space prefix that was not found. 429 | // although technically it would be incorrect. 430 | 431 | // Pick a name. We try to use the final element of the path 432 | // but fall back to _. 433 | prefix := strings.TrimRight(url, "/") 434 | if i := strings.LastIndex(prefix, "/"); i >= 0 { 435 | prefix = prefix[i+1:] 436 | } 437 | if prefix == "" || !isName([]byte(prefix)) || strings.Contains(prefix, ":") { 438 | prefix = "_" 439 | } 440 | if strings.HasPrefix(prefix, "xml") { 441 | // xmlanything is reserved. 442 | prefix = "_" + prefix 443 | } 444 | if p.attrNS[prefix] != "" { 445 | // Name is taken. Find a better one. 446 | for p.seq++; ; p.seq++ { 447 | if id := prefix + "_" + strconv.Itoa(p.seq); p.attrNS[id] == "" { 448 | prefix = id 449 | break 450 | } 451 | } 452 | } 453 | 454 | p.pushPrefix(prefix, url) 455 | } 456 | 457 | // writeNamespaces writes xmlns attributes for all the 458 | // namespace prefixes that have been defined in 459 | // the current element. 460 | func (p *printer) writeNamespaces() { 461 | for i := len(p.prefixes) - 1; i >= 0; i-- { 462 | prefix := p.prefixes[i] 463 | if prefix.mark { 464 | return 465 | } 466 | p.WriteString(" ") 467 | if prefix.prefix == "" { 468 | // Default name space. 469 | p.WriteString(`xmlns="`) 470 | } else { 471 | p.WriteString("xmlns:") 472 | p.WriteString(prefix.prefix) 473 | p.WriteString(`="`) 474 | } 475 | EscapeText(p, []byte(p.nsForPrefix(prefix.prefix))) 476 | p.WriteString(`"`) 477 | } 478 | } 479 | 480 | // pushPrefix pushes a new prefix on the prefix stack 481 | // without checking to see if it is already defined. 482 | func (p *printer) pushPrefix(prefix, url string) { 483 | p.prefixes = append(p.prefixes, printerPrefix{ 484 | prefix: prefix, 485 | url: p.nsForPrefix(prefix), 486 | }) 487 | p.setAttrPrefix(prefix, url) 488 | } 489 | 490 | // nsForPrefix returns the name space for the given 491 | // prefix. Note that this is not valid for the 492 | // empty attribute prefix, which always has an empty 493 | // name space. 494 | func (p *printer) nsForPrefix(prefix string) string { 495 | if prefix == "" { 496 | return p.defaultNS 497 | } 498 | return p.attrNS[prefix] 499 | } 500 | 501 | // markPrefix marks the start of an element on the prefix 502 | // stack. 503 | func (p *printer) markPrefix() { 504 | p.prefixes = append(p.prefixes, printerPrefix{ 505 | mark: true, 506 | }) 507 | } 508 | 509 | // popPrefix pops all defined prefixes for the current 510 | // element. 511 | func (p *printer) popPrefix() { 512 | for len(p.prefixes) > 0 { 513 | prefix := p.prefixes[len(p.prefixes)-1] 514 | p.prefixes = p.prefixes[:len(p.prefixes)-1] 515 | if prefix.mark { 516 | break 517 | } 518 | p.setAttrPrefix(prefix.prefix, prefix.url) 519 | } 520 | } 521 | 522 | // setAttrPrefix sets an attribute name space prefix. 523 | // If url is empty, the attribute is removed. 524 | // If prefix is empty, the default name space is set. 525 | func (p *printer) setAttrPrefix(prefix, url string) { 526 | if prefix == "" { 527 | p.defaultNS = url 528 | return 529 | } 530 | if url == "" { 531 | delete(p.attrPrefix, p.attrNS[prefix]) 532 | delete(p.attrNS, prefix) 533 | return 534 | } 535 | if p.attrPrefix == nil { 536 | // Need to define a new name space. 537 | p.attrPrefix = make(map[string]string) 538 | p.attrNS = make(map[string]string) 539 | } 540 | // Remove any old prefix value. This is OK because we maintain a 541 | // strict one-to-one mapping between prefix and URL (see 542 | // defineNS) 543 | delete(p.attrPrefix, p.attrNS[prefix]) 544 | p.attrPrefix[url] = prefix 545 | p.attrNS[prefix] = url 546 | } 547 | 548 | var ( 549 | marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() 550 | marshalerAttrType = reflect.TypeOf((*MarshalerAttr)(nil)).Elem() 551 | textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() 552 | ) 553 | 554 | // marshalValue writes one or more XML elements representing val. 555 | // If val was obtained from a struct field, finfo must have its details. 556 | func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplate *StartElement) error { 557 | if startTemplate != nil && startTemplate.Name.Local == "" { 558 | return fmt.Errorf("xml: EncodeElement of StartElement with missing name") 559 | } 560 | 561 | if !val.IsValid() { 562 | return nil 563 | } 564 | if finfo != nil && finfo.flags&fOmitEmpty != 0 && isEmptyValue(val) { 565 | return nil 566 | } 567 | 568 | // Drill into interfaces and pointers. 569 | // This can turn into an infinite loop given a cyclic chain, 570 | // but it matches the Go 1 behavior. 571 | for val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr { 572 | if val.IsNil() { 573 | return nil 574 | } 575 | val = val.Elem() 576 | } 577 | 578 | kind := val.Kind() 579 | typ := val.Type() 580 | 581 | // Check for marshaler. 582 | if val.CanInterface() && typ.Implements(marshalerType) { 583 | return p.marshalInterface(val.Interface().(Marshaler), p.defaultStart(typ, finfo, startTemplate)) 584 | } 585 | if val.CanAddr() { 586 | pv := val.Addr() 587 | if pv.CanInterface() && pv.Type().Implements(marshalerType) { 588 | return p.marshalInterface(pv.Interface().(Marshaler), p.defaultStart(pv.Type(), finfo, startTemplate)) 589 | } 590 | } 591 | 592 | // Check for text marshaler. 593 | if val.CanInterface() && typ.Implements(textMarshalerType) { 594 | return p.marshalTextInterface(val.Interface().(encoding.TextMarshaler), p.defaultStart(typ, finfo, startTemplate)) 595 | } 596 | if val.CanAddr() { 597 | pv := val.Addr() 598 | if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { 599 | return p.marshalTextInterface(pv.Interface().(encoding.TextMarshaler), p.defaultStart(pv.Type(), finfo, startTemplate)) 600 | } 601 | } 602 | 603 | // Slices and arrays iterate over the elements. They do not have an enclosing tag. 604 | if (kind == reflect.Slice || kind == reflect.Array) && typ.Elem().Kind() != reflect.Uint8 { 605 | for i, n := 0, val.Len(); i < n; i++ { 606 | if err := p.marshalValue(val.Index(i), finfo, startTemplate); err != nil { 607 | return err 608 | } 609 | } 610 | return nil 611 | } 612 | 613 | tinfo, err := getTypeInfo(typ) 614 | if err != nil { 615 | return err 616 | } 617 | 618 | // Create start element. 619 | // Precedence for the XML element name is: 620 | // 0. startTemplate 621 | // 1. XMLName field in underlying struct; 622 | // 2. field name/tag in the struct field; and 623 | // 3. type name 624 | var start StartElement 625 | 626 | // explicitNS records whether the element's name space has been 627 | // explicitly set (for example an XMLName field). 628 | explicitNS := false 629 | 630 | if startTemplate != nil { 631 | start.Name = startTemplate.Name 632 | explicitNS = true 633 | start.Attr = append(start.Attr, startTemplate.Attr...) 634 | } else if tinfo.xmlname != nil { 635 | xmlname := tinfo.xmlname 636 | if xmlname.name != "" { 637 | start.Name.Space, start.Name.Local = xmlname.xmlns, xmlname.name 638 | } else if v, ok := xmlname.value(val).Interface().(Name); ok && v.Local != "" { 639 | start.Name = v 640 | } 641 | explicitNS = true 642 | } 643 | if start.Name.Local == "" && finfo != nil { 644 | start.Name.Local = finfo.name 645 | if finfo.xmlns != "" { 646 | start.Name.Space = finfo.xmlns 647 | explicitNS = true 648 | } 649 | } 650 | if start.Name.Local == "" { 651 | name := typ.Name() 652 | if name == "" { 653 | return &UnsupportedTypeError{typ} 654 | } 655 | start.Name.Local = name 656 | } 657 | 658 | // defaultNS records the default name space as set by a xmlns="..." 659 | // attribute. We don't set p.defaultNS because we want to let 660 | // the attribute writing code (in p.defineNS) be solely responsible 661 | // for maintaining that. 662 | defaultNS := p.defaultNS 663 | 664 | // Attributes 665 | for i := range tinfo.fields { 666 | finfo := &tinfo.fields[i] 667 | if finfo.flags&fAttr == 0 { 668 | continue 669 | } 670 | attr, err := p.fieldAttr(finfo, val) 671 | if err != nil { 672 | return err 673 | } 674 | if attr.Name.Local == "" { 675 | continue 676 | } 677 | start.Attr = append(start.Attr, attr) 678 | if attr.Name.Space == "" && attr.Name.Local == "xmlns" { 679 | defaultNS = attr.Value 680 | } 681 | } 682 | if !explicitNS { 683 | // Historic behavior: elements use the default name space 684 | // they are contained in by default. 685 | start.Name.Space = defaultNS 686 | } 687 | // Historic behaviour: an element that's in a namespace sets 688 | // the default namespace for all elements contained within it. 689 | start.setDefaultNamespace() 690 | 691 | if err := p.writeStart(&start); err != nil { 692 | return err 693 | } 694 | 695 | if val.Kind() == reflect.Struct { 696 | err = p.marshalStruct(tinfo, val) 697 | } else { 698 | s, b, err1 := p.marshalSimple(typ, val) 699 | if err1 != nil { 700 | err = err1 701 | } else if b != nil { 702 | EscapeText(p, b) 703 | } else { 704 | p.EscapeString(s) 705 | } 706 | } 707 | if err != nil { 708 | return err 709 | } 710 | 711 | if err := p.writeEnd(start.Name); err != nil { 712 | return err 713 | } 714 | 715 | return p.cachedWriteError() 716 | } 717 | 718 | // fieldAttr returns the attribute of the given field. 719 | // If the returned attribute has an empty Name.Local, 720 | // it should not be used. 721 | // The given value holds the value containing the field. 722 | func (p *printer) fieldAttr(finfo *fieldInfo, val reflect.Value) (Attr, error) { 723 | fv := finfo.value(val) 724 | name := Name{Space: finfo.xmlns, Local: finfo.name} 725 | if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) { 726 | return Attr{}, nil 727 | } 728 | if fv.Kind() == reflect.Interface && fv.IsNil() { 729 | return Attr{}, nil 730 | } 731 | if fv.CanInterface() && fv.Type().Implements(marshalerAttrType) { 732 | attr, err := fv.Interface().(MarshalerAttr).MarshalXMLAttr(name) 733 | return attr, err 734 | } 735 | if fv.CanAddr() { 736 | pv := fv.Addr() 737 | if pv.CanInterface() && pv.Type().Implements(marshalerAttrType) { 738 | attr, err := pv.Interface().(MarshalerAttr).MarshalXMLAttr(name) 739 | return attr, err 740 | } 741 | } 742 | if fv.CanInterface() && fv.Type().Implements(textMarshalerType) { 743 | text, err := fv.Interface().(encoding.TextMarshaler).MarshalText() 744 | if err != nil { 745 | return Attr{}, err 746 | } 747 | return Attr{name, string(text)}, nil 748 | } 749 | if fv.CanAddr() { 750 | pv := fv.Addr() 751 | if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { 752 | text, err := pv.Interface().(encoding.TextMarshaler).MarshalText() 753 | if err != nil { 754 | return Attr{}, err 755 | } 756 | return Attr{name, string(text)}, nil 757 | } 758 | } 759 | // Dereference or skip nil pointer, interface values. 760 | switch fv.Kind() { 761 | case reflect.Ptr, reflect.Interface: 762 | if fv.IsNil() { 763 | return Attr{}, nil 764 | } 765 | fv = fv.Elem() 766 | } 767 | s, b, err := p.marshalSimple(fv.Type(), fv) 768 | if err != nil { 769 | return Attr{}, err 770 | } 771 | if b != nil { 772 | s = string(b) 773 | } 774 | return Attr{name, s}, nil 775 | } 776 | 777 | // defaultStart returns the default start element to use, 778 | // given the reflect type, field info, and start template. 779 | func (p *printer) defaultStart(typ reflect.Type, finfo *fieldInfo, startTemplate *StartElement) StartElement { 780 | var start StartElement 781 | // Precedence for the XML element name is as above, 782 | // except that we do not look inside structs for the first field. 783 | if startTemplate != nil { 784 | start.Name = startTemplate.Name 785 | start.Attr = append(start.Attr, startTemplate.Attr...) 786 | } else if finfo != nil && finfo.name != "" { 787 | start.Name.Local = finfo.name 788 | start.Name.Space = finfo.xmlns 789 | } else if typ.Name() != "" { 790 | start.Name.Local = typ.Name() 791 | } else { 792 | // Must be a pointer to a named type, 793 | // since it has the Marshaler methods. 794 | start.Name.Local = typ.Elem().Name() 795 | } 796 | // Historic behaviour: elements use the name space of 797 | // the element they are contained in by default. 798 | if start.Name.Space == "" { 799 | start.Name.Space = p.defaultNS 800 | } 801 | start.setDefaultNamespace() 802 | return start 803 | } 804 | 805 | // marshalInterface marshals a Marshaler interface value. 806 | func (p *printer) marshalInterface(val Marshaler, start StartElement) error { 807 | // Push a marker onto the tag stack so that MarshalXML 808 | // cannot close the XML tags that it did not open. 809 | p.tags = append(p.tags, Name{}) 810 | n := len(p.tags) 811 | 812 | err := val.MarshalXML(p.encoder, start) 813 | if err != nil { 814 | return err 815 | } 816 | 817 | // Make sure MarshalXML closed all its tags. p.tags[n-1] is the mark. 818 | if len(p.tags) > n { 819 | return fmt.Errorf("xml: %s.MarshalXML wrote invalid XML: <%s> not closed", receiverType(val), p.tags[len(p.tags)-1].Local) 820 | } 821 | p.tags = p.tags[:n-1] 822 | return nil 823 | } 824 | 825 | // marshalTextInterface marshals a TextMarshaler interface value. 826 | func (p *printer) marshalTextInterface(val encoding.TextMarshaler, start StartElement) error { 827 | if err := p.writeStart(&start); err != nil { 828 | return err 829 | } 830 | text, err := val.MarshalText() 831 | if err != nil { 832 | return err 833 | } 834 | EscapeText(p, text) 835 | return p.writeEnd(start.Name) 836 | } 837 | 838 | // writeStart writes the given start element. 839 | func (p *printer) writeStart(start *StartElement) error { 840 | if start.Name.Local == "" { 841 | return fmt.Errorf("xml: start tag with no name") 842 | } 843 | 844 | p.tags = append(p.tags, start.Name) 845 | p.markPrefix() 846 | // Define any name spaces explicitly declared in the attributes. 847 | // We do this as a separate pass so that explicitly declared prefixes 848 | // will take precedence over implicitly declared prefixes 849 | // regardless of the order of the attributes. 850 | ignoreNonEmptyDefault := start.Name.Space == "" 851 | for _, attr := range start.Attr { 852 | if err := p.defineNS(attr, ignoreNonEmptyDefault); err != nil { 853 | return err 854 | } 855 | } 856 | // Define any new name spaces implied by the attributes. 857 | for _, attr := range start.Attr { 858 | name := attr.Name 859 | // From http://www.w3.org/TR/xml-names11/#defaulting 860 | // "Default namespace declarations do not apply directly 861 | // to attribute names; the interpretation of unprefixed 862 | // attributes is determined by the element on which they 863 | // appear." 864 | // This means we don't need to create a new namespace 865 | // when an attribute name space is empty. 866 | if name.Space != "" && !name.isNamespace() { 867 | p.createNSPrefix(name.Space, true) 868 | } 869 | } 870 | p.createNSPrefix(start.Name.Space, false) 871 | 872 | p.writeIndent(1) 873 | p.WriteByte('<') 874 | p.writeName(start.Name, false) 875 | p.writeNamespaces() 876 | for _, attr := range start.Attr { 877 | name := attr.Name 878 | if name.Local == "" || name.isNamespace() { 879 | // Namespaces have already been written by writeNamespaces above. 880 | continue 881 | } 882 | p.WriteByte(' ') 883 | p.writeName(name, true) 884 | p.WriteString(`="`) 885 | p.EscapeString(attr.Value) 886 | p.WriteByte('"') 887 | } 888 | p.WriteByte('>') 889 | return nil 890 | } 891 | 892 | // writeName writes the given name. It assumes 893 | // that p.createNSPrefix(name) has already been called. 894 | func (p *printer) writeName(name Name, isAttr bool) { 895 | if prefix := p.prefixForNS(name.Space, isAttr); prefix != "" { 896 | p.WriteString(prefix) 897 | p.WriteByte(':') 898 | } 899 | p.WriteString(name.Local) 900 | } 901 | 902 | func (p *printer) writeEnd(name Name) error { 903 | if name.Local == "" { 904 | return fmt.Errorf("xml: end tag with no name") 905 | } 906 | if len(p.tags) == 0 || p.tags[len(p.tags)-1].Local == "" { 907 | return fmt.Errorf("xml: end tag without start tag", name.Local) 908 | } 909 | if top := p.tags[len(p.tags)-1]; top != name { 910 | if top.Local != name.Local { 911 | return fmt.Errorf("xml: end tag does not match start tag <%s>", name.Local, top.Local) 912 | } 913 | return fmt.Errorf("xml: end tag in namespace %s does not match start tag <%s> in namespace %s", name.Local, name.Space, top.Local, top.Space) 914 | } 915 | p.tags = p.tags[:len(p.tags)-1] 916 | 917 | p.writeIndent(-1) 918 | p.WriteByte('<') 919 | p.WriteByte('/') 920 | p.writeName(name, false) 921 | p.WriteByte('>') 922 | p.popPrefix() 923 | return nil 924 | } 925 | 926 | func (p *printer) marshalSimple(typ reflect.Type, val reflect.Value) (string, []byte, error) { 927 | switch val.Kind() { 928 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 929 | return strconv.FormatInt(val.Int(), 10), nil, nil 930 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 931 | return strconv.FormatUint(val.Uint(), 10), nil, nil 932 | case reflect.Float32, reflect.Float64: 933 | return strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()), nil, nil 934 | case reflect.String: 935 | return val.String(), nil, nil 936 | case reflect.Bool: 937 | return strconv.FormatBool(val.Bool()), nil, nil 938 | case reflect.Array: 939 | if typ.Elem().Kind() != reflect.Uint8 { 940 | break 941 | } 942 | // [...]byte 943 | var bytes []byte 944 | if val.CanAddr() { 945 | bytes = val.Slice(0, val.Len()).Bytes() 946 | } else { 947 | bytes = make([]byte, val.Len()) 948 | reflect.Copy(reflect.ValueOf(bytes), val) 949 | } 950 | return "", bytes, nil 951 | case reflect.Slice: 952 | if typ.Elem().Kind() != reflect.Uint8 { 953 | break 954 | } 955 | // []byte 956 | return "", val.Bytes(), nil 957 | } 958 | return "", nil, &UnsupportedTypeError{typ} 959 | } 960 | 961 | var ddBytes = []byte("--") 962 | 963 | func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error { 964 | s := parentStack{p: p} 965 | for i := range tinfo.fields { 966 | finfo := &tinfo.fields[i] 967 | if finfo.flags&fAttr != 0 { 968 | continue 969 | } 970 | vf := finfo.value(val) 971 | 972 | // Dereference or skip nil pointer, interface values. 973 | switch vf.Kind() { 974 | case reflect.Ptr, reflect.Interface: 975 | if !vf.IsNil() { 976 | vf = vf.Elem() 977 | } 978 | } 979 | 980 | switch finfo.flags & fMode { 981 | case fCharData: 982 | if err := s.setParents(&noField, reflect.Value{}); err != nil { 983 | return err 984 | } 985 | if vf.CanInterface() && vf.Type().Implements(textMarshalerType) { 986 | data, err := vf.Interface().(encoding.TextMarshaler).MarshalText() 987 | if err != nil { 988 | return err 989 | } 990 | Escape(p, data) 991 | continue 992 | } 993 | if vf.CanAddr() { 994 | pv := vf.Addr() 995 | if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { 996 | data, err := pv.Interface().(encoding.TextMarshaler).MarshalText() 997 | if err != nil { 998 | return err 999 | } 1000 | Escape(p, data) 1001 | continue 1002 | } 1003 | } 1004 | var scratch [64]byte 1005 | switch vf.Kind() { 1006 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 1007 | Escape(p, strconv.AppendInt(scratch[:0], vf.Int(), 10)) 1008 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 1009 | Escape(p, strconv.AppendUint(scratch[:0], vf.Uint(), 10)) 1010 | case reflect.Float32, reflect.Float64: 1011 | Escape(p, strconv.AppendFloat(scratch[:0], vf.Float(), 'g', -1, vf.Type().Bits())) 1012 | case reflect.Bool: 1013 | Escape(p, strconv.AppendBool(scratch[:0], vf.Bool())) 1014 | case reflect.String: 1015 | if err := EscapeText(p, []byte(vf.String())); err != nil { 1016 | return err 1017 | } 1018 | case reflect.Slice: 1019 | if elem, ok := vf.Interface().([]byte); ok { 1020 | if err := EscapeText(p, elem); err != nil { 1021 | return err 1022 | } 1023 | } 1024 | } 1025 | continue 1026 | 1027 | case fComment: 1028 | if err := s.setParents(&noField, reflect.Value{}); err != nil { 1029 | return err 1030 | } 1031 | k := vf.Kind() 1032 | if !(k == reflect.String || k == reflect.Slice && vf.Type().Elem().Kind() == reflect.Uint8) { 1033 | return fmt.Errorf("xml: bad type for comment field of %s", val.Type()) 1034 | } 1035 | if vf.Len() == 0 { 1036 | continue 1037 | } 1038 | p.writeIndent(0) 1039 | p.WriteString("" is invalid grammar. Make it "- -->" 1065 | p.WriteByte(' ') 1066 | } 1067 | p.WriteString("-->") 1068 | continue 1069 | 1070 | case fInnerXml: 1071 | iface := vf.Interface() 1072 | switch raw := iface.(type) { 1073 | case []byte: 1074 | p.Write(raw) 1075 | continue 1076 | case string: 1077 | p.WriteString(raw) 1078 | continue 1079 | } 1080 | 1081 | case fElement, fElement | fAny: 1082 | if err := s.setParents(finfo, vf); err != nil { 1083 | return err 1084 | } 1085 | } 1086 | if err := p.marshalValue(vf, finfo, nil); err != nil { 1087 | return err 1088 | } 1089 | } 1090 | if err := s.setParents(&noField, reflect.Value{}); err != nil { 1091 | return err 1092 | } 1093 | return p.cachedWriteError() 1094 | } 1095 | 1096 | var noField fieldInfo 1097 | 1098 | // return the bufio Writer's cached write error 1099 | func (p *printer) cachedWriteError() error { 1100 | _, err := p.Write(nil) 1101 | return err 1102 | } 1103 | 1104 | func (p *printer) writeIndent(depthDelta int) { 1105 | if len(p.prefix) == 0 && len(p.indent) == 0 { 1106 | return 1107 | } 1108 | if depthDelta < 0 { 1109 | p.depth-- 1110 | if p.indentedIn { 1111 | p.indentedIn = false 1112 | return 1113 | } 1114 | p.indentedIn = false 1115 | } 1116 | if p.putNewline { 1117 | p.WriteByte('\n') 1118 | } else { 1119 | p.putNewline = true 1120 | } 1121 | if len(p.prefix) > 0 { 1122 | p.WriteString(p.prefix) 1123 | } 1124 | if len(p.indent) > 0 { 1125 | for i := 0; i < p.depth; i++ { 1126 | p.WriteString(p.indent) 1127 | } 1128 | } 1129 | if depthDelta > 0 { 1130 | p.depth++ 1131 | p.indentedIn = true 1132 | } 1133 | } 1134 | 1135 | type parentStack struct { 1136 | p *printer 1137 | xmlns string 1138 | parents []string 1139 | } 1140 | 1141 | // setParents sets the stack of current parents to those found in finfo. 1142 | // It only writes the start elements if vf holds a non-nil value. 1143 | // If finfo is &noField, it pops all elements. 1144 | func (s *parentStack) setParents(finfo *fieldInfo, vf reflect.Value) error { 1145 | xmlns := s.p.defaultNS 1146 | if finfo.xmlns != "" { 1147 | xmlns = finfo.xmlns 1148 | } 1149 | commonParents := 0 1150 | if xmlns == s.xmlns { 1151 | for ; commonParents < len(finfo.parents) && commonParents < len(s.parents); commonParents++ { 1152 | if finfo.parents[commonParents] != s.parents[commonParents] { 1153 | break 1154 | } 1155 | } 1156 | } 1157 | // Pop off any parents that aren't in common with the previous field. 1158 | for i := len(s.parents) - 1; i >= commonParents; i-- { 1159 | if err := s.p.writeEnd(Name{ 1160 | Space: s.xmlns, 1161 | Local: s.parents[i], 1162 | }); err != nil { 1163 | return err 1164 | } 1165 | } 1166 | s.parents = finfo.parents 1167 | s.xmlns = xmlns 1168 | if commonParents >= len(s.parents) { 1169 | // No new elements to push. 1170 | return nil 1171 | } 1172 | if (vf.Kind() == reflect.Ptr || vf.Kind() == reflect.Interface) && vf.IsNil() { 1173 | // The element is nil, so no need for the start elements. 1174 | s.parents = s.parents[:commonParents] 1175 | return nil 1176 | } 1177 | // Push any new parents required. 1178 | for _, name := range s.parents[commonParents:] { 1179 | start := &StartElement{ 1180 | Name: Name{ 1181 | Space: s.xmlns, 1182 | Local: name, 1183 | }, 1184 | } 1185 | // Set the default name space for parent elements 1186 | // to match what we do with other elements. 1187 | if s.xmlns != s.p.defaultNS { 1188 | start.setDefaultNamespace() 1189 | } 1190 | if err := s.p.writeStart(start); err != nil { 1191 | return err 1192 | } 1193 | } 1194 | return nil 1195 | } 1196 | 1197 | // A MarshalXMLError is returned when Marshal encounters a type 1198 | // that cannot be converted into XML. 1199 | type UnsupportedTypeError struct { 1200 | Type reflect.Type 1201 | } 1202 | 1203 | func (e *UnsupportedTypeError) Error() string { 1204 | return "xml: unsupported type: " + e.Type.String() 1205 | } 1206 | 1207 | func isEmptyValue(v reflect.Value) bool { 1208 | switch v.Kind() { 1209 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 1210 | return v.Len() == 0 1211 | case reflect.Bool: 1212 | return !v.Bool() 1213 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 1214 | return v.Int() == 0 1215 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 1216 | return v.Uint() == 0 1217 | case reflect.Float32, reflect.Float64: 1218 | return v.Float() == 0 1219 | case reflect.Interface, reflect.Ptr: 1220 | return v.IsNil() 1221 | } 1222 | return false 1223 | } 1224 | -------------------------------------------------------------------------------- /webdav/internal/xml/read.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xml 6 | 7 | import ( 8 | "bytes" 9 | "encoding" 10 | "errors" 11 | "fmt" 12 | "reflect" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | // BUG(rsc): Mapping between XML elements and data structures is inherently flawed: 18 | // an XML element is an order-dependent collection of anonymous 19 | // values, while a data structure is an order-independent collection 20 | // of named values. 21 | // See package json for a textual representation more suitable 22 | // to data structures. 23 | 24 | // Unmarshal parses the XML-encoded data and stores the result in 25 | // the value pointed to by v, which must be an arbitrary struct, 26 | // slice, or string. Well-formed data that does not fit into v is 27 | // discarded. 28 | // 29 | // Because Unmarshal uses the reflect package, it can only assign 30 | // to exported (upper case) fields. Unmarshal uses a case-sensitive 31 | // comparison to match XML element names to tag values and struct 32 | // field names. 33 | // 34 | // Unmarshal maps an XML element to a struct using the following rules. 35 | // In the rules, the tag of a field refers to the value associated with the 36 | // key 'xml' in the struct field's tag (see the example above). 37 | // 38 | // - If the struct has a field of type []byte or string with tag 39 | // ",innerxml", Unmarshal accumulates the raw XML nested inside the 40 | // element in that field. The rest of the rules still apply. 41 | // 42 | // - If the struct has a field named XMLName of type xml.Name, 43 | // Unmarshal records the element name in that field. 44 | // 45 | // - If the XMLName field has an associated tag of the form 46 | // "name" or "namespace-URL name", the XML element must have 47 | // the given name (and, optionally, name space) or else Unmarshal 48 | // returns an error. 49 | // 50 | // - If the XML element has an attribute whose name matches a 51 | // struct field name with an associated tag containing ",attr" or 52 | // the explicit name in a struct field tag of the form "name,attr", 53 | // Unmarshal records the attribute value in that field. 54 | // 55 | // - If the XML element contains character data, that data is 56 | // accumulated in the first struct field that has tag ",chardata". 57 | // The struct field may have type []byte or string. 58 | // If there is no such field, the character data is discarded. 59 | // 60 | // - If the XML element contains comments, they are accumulated in 61 | // the first struct field that has tag ",comment". The struct 62 | // field may have type []byte or string. If there is no such 63 | // field, the comments are discarded. 64 | // 65 | // - If the XML element contains a sub-element whose name matches 66 | // the prefix of a tag formatted as "a" or "a>b>c", unmarshal 67 | // will descend into the XML structure looking for elements with the 68 | // given names, and will map the innermost elements to that struct 69 | // field. A tag starting with ">" is equivalent to one starting 70 | // with the field name followed by ">". 71 | // 72 | // - If the XML element contains a sub-element whose name matches 73 | // a struct field's XMLName tag and the struct field has no 74 | // explicit name tag as per the previous rule, unmarshal maps 75 | // the sub-element to that struct field. 76 | // 77 | // - If the XML element contains a sub-element whose name matches a 78 | // field without any mode flags (",attr", ",chardata", etc), Unmarshal 79 | // maps the sub-element to that struct field. 80 | // 81 | // - If the XML element contains a sub-element that hasn't matched any 82 | // of the above rules and the struct has a field with tag ",any", 83 | // unmarshal maps the sub-element to that struct field. 84 | // 85 | // - An anonymous struct field is handled as if the fields of its 86 | // value were part of the outer struct. 87 | // 88 | // - A struct field with tag "-" is never unmarshalled into. 89 | // 90 | // Unmarshal maps an XML element to a string or []byte by saving the 91 | // concatenation of that element's character data in the string or 92 | // []byte. The saved []byte is never nil. 93 | // 94 | // Unmarshal maps an attribute value to a string or []byte by saving 95 | // the value in the string or slice. 96 | // 97 | // Unmarshal maps an XML element to a slice by extending the length of 98 | // the slice and mapping the element to the newly created value. 99 | // 100 | // Unmarshal maps an XML element or attribute value to a bool by 101 | // setting it to the boolean value represented by the string. 102 | // 103 | // Unmarshal maps an XML element or attribute value to an integer or 104 | // floating-point field by setting the field to the result of 105 | // interpreting the string value in decimal. There is no check for 106 | // overflow. 107 | // 108 | // Unmarshal maps an XML element to an xml.Name by recording the 109 | // element name. 110 | // 111 | // Unmarshal maps an XML element to a pointer by setting the pointer 112 | // to a freshly allocated value and then mapping the element to that value. 113 | func Unmarshal(data []byte, v interface{}) error { 114 | return NewDecoder(bytes.NewReader(data)).Decode(v) 115 | } 116 | 117 | // Decode works like xml.Unmarshal, except it reads the decoder 118 | // stream to find the start element. 119 | func (d *Decoder) Decode(v interface{}) error { 120 | return d.DecodeElement(v, nil) 121 | } 122 | 123 | // DecodeElement works like xml.Unmarshal except that it takes 124 | // a pointer to the start XML element to decode into v. 125 | // It is useful when a client reads some raw XML tokens itself 126 | // but also wants to defer to Unmarshal for some elements. 127 | func (d *Decoder) DecodeElement(v interface{}, start *StartElement) error { 128 | val := reflect.ValueOf(v) 129 | if val.Kind() != reflect.Ptr { 130 | return errors.New("non-pointer passed to Unmarshal") 131 | } 132 | return d.unmarshal(val.Elem(), start) 133 | } 134 | 135 | // An UnmarshalError represents an error in the unmarshalling process. 136 | type UnmarshalError string 137 | 138 | func (e UnmarshalError) Error() string { return string(e) } 139 | 140 | // Unmarshaler is the interface implemented by objects that can unmarshal 141 | // an XML element description of themselves. 142 | // 143 | // UnmarshalXML decodes a single XML element 144 | // beginning with the given start element. 145 | // If it returns an error, the outer call to Unmarshal stops and 146 | // returns that error. 147 | // UnmarshalXML must consume exactly one XML element. 148 | // One common implementation strategy is to unmarshal into 149 | // a separate value with a layout matching the expected XML 150 | // using d.DecodeElement, and then to copy the data from 151 | // that value into the receiver. 152 | // Another common strategy is to use d.Token to process the 153 | // XML object one token at a time. 154 | // UnmarshalXML may not use d.RawToken. 155 | type Unmarshaler interface { 156 | UnmarshalXML(d *Decoder, start StartElement) error 157 | } 158 | 159 | // UnmarshalerAttr is the interface implemented by objects that can unmarshal 160 | // an XML attribute description of themselves. 161 | // 162 | // UnmarshalXMLAttr decodes a single XML attribute. 163 | // If it returns an error, the outer call to Unmarshal stops and 164 | // returns that error. 165 | // UnmarshalXMLAttr is used only for struct fields with the 166 | // "attr" option in the field tag. 167 | type UnmarshalerAttr interface { 168 | UnmarshalXMLAttr(attr Attr) error 169 | } 170 | 171 | // receiverType returns the receiver type to use in an expression like "%s.MethodName". 172 | func receiverType(val interface{}) string { 173 | t := reflect.TypeOf(val) 174 | if t.Name() != "" { 175 | return t.String() 176 | } 177 | return "(" + t.String() + ")" 178 | } 179 | 180 | // unmarshalInterface unmarshals a single XML element into val. 181 | // start is the opening tag of the element. 182 | func (p *Decoder) unmarshalInterface(val Unmarshaler, start *StartElement) error { 183 | // Record that decoder must stop at end tag corresponding to start. 184 | p.pushEOF() 185 | 186 | p.unmarshalDepth++ 187 | err := val.UnmarshalXML(p, *start) 188 | p.unmarshalDepth-- 189 | if err != nil { 190 | p.popEOF() 191 | return err 192 | } 193 | 194 | if !p.popEOF() { 195 | return fmt.Errorf("xml: %s.UnmarshalXML did not consume entire <%s> element", receiverType(val), start.Name.Local) 196 | } 197 | 198 | return nil 199 | } 200 | 201 | // unmarshalTextInterface unmarshals a single XML element into val. 202 | // The chardata contained in the element (but not its children) 203 | // is passed to the text unmarshaler. 204 | func (p *Decoder) unmarshalTextInterface(val encoding.TextUnmarshaler, start *StartElement) error { 205 | var buf []byte 206 | depth := 1 207 | for depth > 0 { 208 | t, err := p.Token() 209 | if err != nil { 210 | return err 211 | } 212 | switch t := t.(type) { 213 | case CharData: 214 | if depth == 1 { 215 | buf = append(buf, t...) 216 | } 217 | case StartElement: 218 | depth++ 219 | case EndElement: 220 | depth-- 221 | } 222 | } 223 | return val.UnmarshalText(buf) 224 | } 225 | 226 | // unmarshalAttr unmarshals a single XML attribute into val. 227 | func (p *Decoder) unmarshalAttr(val reflect.Value, attr Attr) error { 228 | if val.Kind() == reflect.Ptr { 229 | if val.IsNil() { 230 | val.Set(reflect.New(val.Type().Elem())) 231 | } 232 | val = val.Elem() 233 | } 234 | 235 | if val.CanInterface() && val.Type().Implements(unmarshalerAttrType) { 236 | // This is an unmarshaler with a non-pointer receiver, 237 | // so it's likely to be incorrect, but we do what we're told. 238 | return val.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr) 239 | } 240 | if val.CanAddr() { 241 | pv := val.Addr() 242 | if pv.CanInterface() && pv.Type().Implements(unmarshalerAttrType) { 243 | return pv.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr) 244 | } 245 | } 246 | 247 | // Not an UnmarshalerAttr; try encoding.TextUnmarshaler. 248 | if val.CanInterface() && val.Type().Implements(textUnmarshalerType) { 249 | // This is an unmarshaler with a non-pointer receiver, 250 | // so it's likely to be incorrect, but we do what we're told. 251 | return val.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value)) 252 | } 253 | if val.CanAddr() { 254 | pv := val.Addr() 255 | if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) { 256 | return pv.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value)) 257 | } 258 | } 259 | 260 | copyValue(val, []byte(attr.Value)) 261 | return nil 262 | } 263 | 264 | var ( 265 | unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() 266 | unmarshalerAttrType = reflect.TypeOf((*UnmarshalerAttr)(nil)).Elem() 267 | textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() 268 | ) 269 | 270 | // Unmarshal a single XML element into val. 271 | func (p *Decoder) unmarshal(val reflect.Value, start *StartElement) error { 272 | // Find start element if we need it. 273 | if start == nil { 274 | for { 275 | tok, err := p.Token() 276 | if err != nil { 277 | return err 278 | } 279 | if t, ok := tok.(StartElement); ok { 280 | start = &t 281 | break 282 | } 283 | } 284 | } 285 | 286 | // Load value from interface, but only if the result will be 287 | // usefully addressable. 288 | if val.Kind() == reflect.Interface && !val.IsNil() { 289 | e := val.Elem() 290 | if e.Kind() == reflect.Ptr && !e.IsNil() { 291 | val = e 292 | } 293 | } 294 | 295 | if val.Kind() == reflect.Ptr { 296 | if val.IsNil() { 297 | val.Set(reflect.New(val.Type().Elem())) 298 | } 299 | val = val.Elem() 300 | } 301 | 302 | if val.CanInterface() && val.Type().Implements(unmarshalerType) { 303 | // This is an unmarshaler with a non-pointer receiver, 304 | // so it's likely to be incorrect, but we do what we're told. 305 | return p.unmarshalInterface(val.Interface().(Unmarshaler), start) 306 | } 307 | 308 | if val.CanAddr() { 309 | pv := val.Addr() 310 | if pv.CanInterface() && pv.Type().Implements(unmarshalerType) { 311 | return p.unmarshalInterface(pv.Interface().(Unmarshaler), start) 312 | } 313 | } 314 | 315 | if val.CanInterface() && val.Type().Implements(textUnmarshalerType) { 316 | return p.unmarshalTextInterface(val.Interface().(encoding.TextUnmarshaler), start) 317 | } 318 | 319 | if val.CanAddr() { 320 | pv := val.Addr() 321 | if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) { 322 | return p.unmarshalTextInterface(pv.Interface().(encoding.TextUnmarshaler), start) 323 | } 324 | } 325 | 326 | var ( 327 | data []byte 328 | saveData reflect.Value 329 | comment []byte 330 | saveComment reflect.Value 331 | saveXML reflect.Value 332 | saveXMLIndex int 333 | saveXMLData []byte 334 | saveAny reflect.Value 335 | sv reflect.Value 336 | tinfo *typeInfo 337 | err error 338 | ) 339 | 340 | switch v := val; v.Kind() { 341 | default: 342 | return errors.New("unknown type " + v.Type().String()) 343 | 344 | case reflect.Interface: 345 | // TODO: For now, simply ignore the field. In the near 346 | // future we may choose to unmarshal the start 347 | // element on it, if not nil. 348 | return p.Skip() 349 | 350 | case reflect.Slice: 351 | typ := v.Type() 352 | if typ.Elem().Kind() == reflect.Uint8 { 353 | // []byte 354 | saveData = v 355 | break 356 | } 357 | 358 | // Slice of element values. 359 | // Grow slice. 360 | n := v.Len() 361 | if n >= v.Cap() { 362 | ncap := 2 * n 363 | if ncap < 4 { 364 | ncap = 4 365 | } 366 | new := reflect.MakeSlice(typ, n, ncap) 367 | reflect.Copy(new, v) 368 | v.Set(new) 369 | } 370 | v.SetLen(n + 1) 371 | 372 | // Recur to read element into slice. 373 | if err := p.unmarshal(v.Index(n), start); err != nil { 374 | v.SetLen(n) 375 | return err 376 | } 377 | return nil 378 | 379 | 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: 380 | saveData = v 381 | 382 | case reflect.Struct: 383 | typ := v.Type() 384 | if typ == nameType { 385 | v.Set(reflect.ValueOf(start.Name)) 386 | break 387 | } 388 | 389 | sv = v 390 | tinfo, err = getTypeInfo(typ) 391 | if err != nil { 392 | return err 393 | } 394 | 395 | // Validate and assign element name. 396 | if tinfo.xmlname != nil { 397 | finfo := tinfo.xmlname 398 | if finfo.name != "" && finfo.name != start.Name.Local { 399 | return UnmarshalError("expected element type <" + finfo.name + "> but have <" + start.Name.Local + ">") 400 | } 401 | if finfo.xmlns != "" && finfo.xmlns != start.Name.Space { 402 | e := "expected element <" + finfo.name + "> in name space " + finfo.xmlns + " but have " 403 | if start.Name.Space == "" { 404 | e += "no name space" 405 | } else { 406 | e += start.Name.Space 407 | } 408 | return UnmarshalError(e) 409 | } 410 | fv := finfo.value(sv) 411 | if _, ok := fv.Interface().(Name); ok { 412 | fv.Set(reflect.ValueOf(start.Name)) 413 | } 414 | } 415 | 416 | // Assign attributes. 417 | // Also, determine whether we need to save character data or comments. 418 | for i := range tinfo.fields { 419 | finfo := &tinfo.fields[i] 420 | switch finfo.flags & fMode { 421 | case fAttr: 422 | strv := finfo.value(sv) 423 | // Look for attribute. 424 | for _, a := range start.Attr { 425 | if a.Name.Local == finfo.name && (finfo.xmlns == "" || finfo.xmlns == a.Name.Space) { 426 | if err := p.unmarshalAttr(strv, a); err != nil { 427 | return err 428 | } 429 | break 430 | } 431 | } 432 | 433 | case fCharData: 434 | if !saveData.IsValid() { 435 | saveData = finfo.value(sv) 436 | } 437 | 438 | case fComment: 439 | if !saveComment.IsValid() { 440 | saveComment = finfo.value(sv) 441 | } 442 | 443 | case fAny, fAny | fElement: 444 | if !saveAny.IsValid() { 445 | saveAny = finfo.value(sv) 446 | } 447 | 448 | case fInnerXml: 449 | if !saveXML.IsValid() { 450 | saveXML = finfo.value(sv) 451 | if p.saved == nil { 452 | saveXMLIndex = 0 453 | p.saved = new(bytes.Buffer) 454 | } else { 455 | saveXMLIndex = p.savedOffset() 456 | } 457 | } 458 | } 459 | } 460 | } 461 | 462 | // Find end element. 463 | // Process sub-elements along the way. 464 | Loop: 465 | for { 466 | var savedOffset int 467 | if saveXML.IsValid() { 468 | savedOffset = p.savedOffset() 469 | } 470 | tok, err := p.Token() 471 | if err != nil { 472 | return err 473 | } 474 | switch t := tok.(type) { 475 | case StartElement: 476 | consumed := false 477 | if sv.IsValid() { 478 | consumed, err = p.unmarshalPath(tinfo, sv, nil, &t) 479 | if err != nil { 480 | return err 481 | } 482 | if !consumed && saveAny.IsValid() { 483 | consumed = true 484 | if err := p.unmarshal(saveAny, &t); err != nil { 485 | return err 486 | } 487 | } 488 | } 489 | if !consumed { 490 | if err := p.Skip(); err != nil { 491 | return err 492 | } 493 | } 494 | 495 | case EndElement: 496 | if saveXML.IsValid() { 497 | saveXMLData = p.saved.Bytes()[saveXMLIndex:savedOffset] 498 | if saveXMLIndex == 0 { 499 | p.saved = nil 500 | } 501 | } 502 | break Loop 503 | 504 | case CharData: 505 | if saveData.IsValid() { 506 | data = append(data, t...) 507 | } 508 | 509 | case Comment: 510 | if saveComment.IsValid() { 511 | comment = append(comment, t...) 512 | } 513 | } 514 | } 515 | 516 | if saveData.IsValid() && saveData.CanInterface() && saveData.Type().Implements(textUnmarshalerType) { 517 | if err := saveData.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil { 518 | return err 519 | } 520 | saveData = reflect.Value{} 521 | } 522 | 523 | if saveData.IsValid() && saveData.CanAddr() { 524 | pv := saveData.Addr() 525 | if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) { 526 | if err := pv.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil { 527 | return err 528 | } 529 | saveData = reflect.Value{} 530 | } 531 | } 532 | 533 | if err := copyValue(saveData, data); err != nil { 534 | return err 535 | } 536 | 537 | switch t := saveComment; t.Kind() { 538 | case reflect.String: 539 | t.SetString(string(comment)) 540 | case reflect.Slice: 541 | t.Set(reflect.ValueOf(comment)) 542 | } 543 | 544 | switch t := saveXML; t.Kind() { 545 | case reflect.String: 546 | t.SetString(string(saveXMLData)) 547 | case reflect.Slice: 548 | t.Set(reflect.ValueOf(saveXMLData)) 549 | } 550 | 551 | return nil 552 | } 553 | 554 | func copyValue(dst reflect.Value, src []byte) (err error) { 555 | dst0 := dst 556 | 557 | if dst.Kind() == reflect.Ptr { 558 | if dst.IsNil() { 559 | dst.Set(reflect.New(dst.Type().Elem())) 560 | } 561 | dst = dst.Elem() 562 | } 563 | 564 | // Save accumulated data. 565 | switch dst.Kind() { 566 | case reflect.Invalid: 567 | // Probably a comment. 568 | default: 569 | return errors.New("cannot unmarshal into " + dst0.Type().String()) 570 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 571 | itmp, err := strconv.ParseInt(string(src), 10, dst.Type().Bits()) 572 | if err != nil { 573 | return err 574 | } 575 | dst.SetInt(itmp) 576 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 577 | utmp, err := strconv.ParseUint(string(src), 10, dst.Type().Bits()) 578 | if err != nil { 579 | return err 580 | } 581 | dst.SetUint(utmp) 582 | case reflect.Float32, reflect.Float64: 583 | ftmp, err := strconv.ParseFloat(string(src), dst.Type().Bits()) 584 | if err != nil { 585 | return err 586 | } 587 | dst.SetFloat(ftmp) 588 | case reflect.Bool: 589 | value, err := strconv.ParseBool(strings.TrimSpace(string(src))) 590 | if err != nil { 591 | return err 592 | } 593 | dst.SetBool(value) 594 | case reflect.String: 595 | dst.SetString(string(src)) 596 | case reflect.Slice: 597 | if len(src) == 0 { 598 | // non-nil to flag presence 599 | src = []byte{} 600 | } 601 | dst.SetBytes(src) 602 | } 603 | return nil 604 | } 605 | 606 | // unmarshalPath walks down an XML structure looking for wanted 607 | // paths, and calls unmarshal on them. 608 | // The consumed result tells whether XML elements have been consumed 609 | // from the Decoder until start's matching end element, or if it's 610 | // still untouched because start is uninteresting for sv's fields. 611 | func (p *Decoder) unmarshalPath(tinfo *typeInfo, sv reflect.Value, parents []string, start *StartElement) (consumed bool, err error) { 612 | recurse := false 613 | Loop: 614 | for i := range tinfo.fields { 615 | finfo := &tinfo.fields[i] 616 | if finfo.flags&fElement == 0 || len(finfo.parents) < len(parents) || finfo.xmlns != "" && finfo.xmlns != start.Name.Space { 617 | continue 618 | } 619 | for j := range parents { 620 | if parents[j] != finfo.parents[j] { 621 | continue Loop 622 | } 623 | } 624 | if len(finfo.parents) == len(parents) && finfo.name == start.Name.Local { 625 | // It's a perfect match, unmarshal the field. 626 | return true, p.unmarshal(finfo.value(sv), start) 627 | } 628 | if len(finfo.parents) > len(parents) && finfo.parents[len(parents)] == start.Name.Local { 629 | // It's a prefix for the field. Break and recurse 630 | // since it's not ok for one field path to be itself 631 | // the prefix for another field path. 632 | recurse = true 633 | 634 | // We can reuse the same slice as long as we 635 | // don't try to append to it. 636 | parents = finfo.parents[:len(parents)+1] 637 | break 638 | } 639 | } 640 | if !recurse { 641 | // We have no business with this element. 642 | return false, nil 643 | } 644 | // The element is not a perfect match for any field, but one 645 | // or more fields have the path to this element as a parent 646 | // prefix. Recurse and attempt to match these. 647 | for { 648 | var tok Token 649 | tok, err = p.Token() 650 | if err != nil { 651 | return true, err 652 | } 653 | switch t := tok.(type) { 654 | case StartElement: 655 | consumed2, err := p.unmarshalPath(tinfo, sv, parents, &t) 656 | if err != nil { 657 | return true, err 658 | } 659 | if !consumed2 { 660 | if err := p.Skip(); err != nil { 661 | return true, err 662 | } 663 | } 664 | case EndElement: 665 | return true, nil 666 | } 667 | } 668 | } 669 | 670 | // Skip reads tokens until it has consumed the end element 671 | // matching the most recent start element already consumed. 672 | // It recurs if it encounters a start element, so it can be used to 673 | // skip nested structures. 674 | // It returns nil if it finds an end element matching the start 675 | // element; otherwise it returns an error describing the problem. 676 | func (d *Decoder) Skip() error { 677 | for { 678 | tok, err := d.Token() 679 | if err != nil { 680 | return err 681 | } 682 | switch tok.(type) { 683 | case StartElement: 684 | if err := d.Skip(); err != nil { 685 | return err 686 | } 687 | case EndElement: 688 | return nil 689 | } 690 | } 691 | } 692 | -------------------------------------------------------------------------------- /webdav/internal/xml/typeinfo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xml 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | // typeInfo holds details for the xml representation of a type. 15 | type typeInfo struct { 16 | xmlname *fieldInfo 17 | fields []fieldInfo 18 | } 19 | 20 | // fieldInfo holds details for the xml representation of a single field. 21 | type fieldInfo struct { 22 | idx []int 23 | name string 24 | xmlns string 25 | flags fieldFlags 26 | parents []string 27 | } 28 | 29 | type fieldFlags int 30 | 31 | const ( 32 | fElement fieldFlags = 1 << iota 33 | fAttr 34 | fCharData 35 | fInnerXml 36 | fComment 37 | fAny 38 | 39 | fOmitEmpty 40 | 41 | fMode = fElement | fAttr | fCharData | fInnerXml | fComment | fAny 42 | ) 43 | 44 | var tinfoMap = make(map[reflect.Type]*typeInfo) 45 | var tinfoLock sync.RWMutex 46 | 47 | var nameType = reflect.TypeOf(Name{}) 48 | 49 | // getTypeInfo returns the typeInfo structure with details necessary 50 | // for marshalling and unmarshalling typ. 51 | func getTypeInfo(typ reflect.Type) (*typeInfo, error) { 52 | tinfoLock.RLock() 53 | tinfo, ok := tinfoMap[typ] 54 | tinfoLock.RUnlock() 55 | if ok { 56 | return tinfo, nil 57 | } 58 | tinfo = &typeInfo{} 59 | if typ.Kind() == reflect.Struct && typ != nameType { 60 | n := typ.NumField() 61 | for i := 0; i < n; i++ { 62 | f := typ.Field(i) 63 | if f.PkgPath != "" || f.Tag.Get("xml") == "-" { 64 | continue // Private field 65 | } 66 | 67 | // For embedded structs, embed its fields. 68 | if f.Anonymous { 69 | t := f.Type 70 | if t.Kind() == reflect.Ptr { 71 | t = t.Elem() 72 | } 73 | if t.Kind() == reflect.Struct { 74 | inner, err := getTypeInfo(t) 75 | if err != nil { 76 | return nil, err 77 | } 78 | if tinfo.xmlname == nil { 79 | tinfo.xmlname = inner.xmlname 80 | } 81 | for _, finfo := range inner.fields { 82 | finfo.idx = append([]int{i}, finfo.idx...) 83 | if err := addFieldInfo(typ, tinfo, &finfo); err != nil { 84 | return nil, err 85 | } 86 | } 87 | continue 88 | } 89 | } 90 | 91 | finfo, err := structFieldInfo(typ, &f) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | if f.Name == "XMLName" { 97 | tinfo.xmlname = finfo 98 | continue 99 | } 100 | 101 | // Add the field if it doesn't conflict with other fields. 102 | if err := addFieldInfo(typ, tinfo, finfo); err != nil { 103 | return nil, err 104 | } 105 | } 106 | } 107 | tinfoLock.Lock() 108 | tinfoMap[typ] = tinfo 109 | tinfoLock.Unlock() 110 | return tinfo, nil 111 | } 112 | 113 | // structFieldInfo builds and returns a fieldInfo for f. 114 | func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) { 115 | finfo := &fieldInfo{idx: f.Index} 116 | 117 | // Split the tag from the xml namespace if necessary. 118 | tag := f.Tag.Get("xml") 119 | if i := strings.Index(tag, " "); i >= 0 { 120 | finfo.xmlns, tag = tag[:i], tag[i+1:] 121 | } 122 | 123 | // Parse flags. 124 | tokens := strings.Split(tag, ",") 125 | if len(tokens) == 1 { 126 | finfo.flags = fElement 127 | } else { 128 | tag = tokens[0] 129 | for _, flag := range tokens[1:] { 130 | switch flag { 131 | case "attr": 132 | finfo.flags |= fAttr 133 | case "chardata": 134 | finfo.flags |= fCharData 135 | case "innerxml": 136 | finfo.flags |= fInnerXml 137 | case "comment": 138 | finfo.flags |= fComment 139 | case "any": 140 | finfo.flags |= fAny 141 | case "omitempty": 142 | finfo.flags |= fOmitEmpty 143 | } 144 | } 145 | 146 | // Validate the flags used. 147 | valid := true 148 | switch mode := finfo.flags & fMode; mode { 149 | case 0: 150 | finfo.flags |= fElement 151 | case fAttr, fCharData, fInnerXml, fComment, fAny: 152 | if f.Name == "XMLName" || tag != "" && mode != fAttr { 153 | valid = false 154 | } 155 | default: 156 | // This will also catch multiple modes in a single field. 157 | valid = false 158 | } 159 | if finfo.flags&fMode == fAny { 160 | finfo.flags |= fElement 161 | } 162 | if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 { 163 | valid = false 164 | } 165 | if !valid { 166 | return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q", 167 | f.Name, typ, f.Tag.Get("xml")) 168 | } 169 | } 170 | 171 | // Use of xmlns without a name is not allowed. 172 | if finfo.xmlns != "" && tag == "" { 173 | return nil, fmt.Errorf("xml: namespace without name in field %s of type %s: %q", 174 | f.Name, typ, f.Tag.Get("xml")) 175 | } 176 | 177 | if f.Name == "XMLName" { 178 | // The XMLName field records the XML element name. Don't 179 | // process it as usual because its name should default to 180 | // empty rather than to the field name. 181 | finfo.name = tag 182 | return finfo, nil 183 | } 184 | 185 | if tag == "" { 186 | // If the name part of the tag is completely empty, get 187 | // default from XMLName of underlying struct if feasible, 188 | // or field name otherwise. 189 | if xmlname := lookupXMLName(f.Type); xmlname != nil { 190 | finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name 191 | } else { 192 | finfo.name = f.Name 193 | } 194 | return finfo, nil 195 | } 196 | 197 | if finfo.xmlns == "" && finfo.flags&fAttr == 0 { 198 | // If it's an element no namespace specified, get the default 199 | // from the XMLName of enclosing struct if possible. 200 | if xmlname := lookupXMLName(typ); xmlname != nil { 201 | finfo.xmlns = xmlname.xmlns 202 | } 203 | } 204 | 205 | // Prepare field name and parents. 206 | parents := strings.Split(tag, ">") 207 | if parents[0] == "" { 208 | parents[0] = f.Name 209 | } 210 | if parents[len(parents)-1] == "" { 211 | return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ) 212 | } 213 | finfo.name = parents[len(parents)-1] 214 | if len(parents) > 1 { 215 | if (finfo.flags & fElement) == 0 { 216 | return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ",")) 217 | } 218 | finfo.parents = parents[:len(parents)-1] 219 | } 220 | 221 | // If the field type has an XMLName field, the names must match 222 | // so that the behavior of both marshalling and unmarshalling 223 | // is straightforward and unambiguous. 224 | if finfo.flags&fElement != 0 { 225 | ftyp := f.Type 226 | xmlname := lookupXMLName(ftyp) 227 | if xmlname != nil && xmlname.name != finfo.name { 228 | return nil, fmt.Errorf("xml: name %q in tag of %s.%s conflicts with name %q in %s.XMLName", 229 | finfo.name, typ, f.Name, xmlname.name, ftyp) 230 | } 231 | } 232 | return finfo, nil 233 | } 234 | 235 | // lookupXMLName returns the fieldInfo for typ's XMLName field 236 | // in case it exists and has a valid xml field tag, otherwise 237 | // it returns nil. 238 | func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) { 239 | for typ.Kind() == reflect.Ptr { 240 | typ = typ.Elem() 241 | } 242 | if typ.Kind() != reflect.Struct { 243 | return nil 244 | } 245 | for i, n := 0, typ.NumField(); i < n; i++ { 246 | f := typ.Field(i) 247 | if f.Name != "XMLName" { 248 | continue 249 | } 250 | finfo, err := structFieldInfo(typ, &f) 251 | if finfo.name != "" && err == nil { 252 | return finfo 253 | } 254 | // Also consider errors as a non-existent field tag 255 | // and let getTypeInfo itself report the error. 256 | break 257 | } 258 | return nil 259 | } 260 | 261 | func min(a, b int) int { 262 | if a <= b { 263 | return a 264 | } 265 | return b 266 | } 267 | 268 | // addFieldInfo adds finfo to tinfo.fields if there are no 269 | // conflicts, or if conflicts arise from previous fields that were 270 | // obtained from deeper embedded structures than finfo. In the latter 271 | // case, the conflicting entries are dropped. 272 | // A conflict occurs when the path (parent + name) to a field is 273 | // itself a prefix of another path, or when two paths match exactly. 274 | // It is okay for field paths to share a common, shorter prefix. 275 | func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error { 276 | var conflicts []int 277 | Loop: 278 | // First, figure all conflicts. Most working code will have none. 279 | for i := range tinfo.fields { 280 | oldf := &tinfo.fields[i] 281 | if oldf.flags&fMode != newf.flags&fMode { 282 | continue 283 | } 284 | if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns { 285 | continue 286 | } 287 | minl := min(len(newf.parents), len(oldf.parents)) 288 | for p := 0; p < minl; p++ { 289 | if oldf.parents[p] != newf.parents[p] { 290 | continue Loop 291 | } 292 | } 293 | if len(oldf.parents) > len(newf.parents) { 294 | if oldf.parents[len(newf.parents)] == newf.name { 295 | conflicts = append(conflicts, i) 296 | } 297 | } else if len(oldf.parents) < len(newf.parents) { 298 | if newf.parents[len(oldf.parents)] == oldf.name { 299 | conflicts = append(conflicts, i) 300 | } 301 | } else { 302 | if newf.name == oldf.name { 303 | conflicts = append(conflicts, i) 304 | } 305 | } 306 | } 307 | // Without conflicts, add the new field and return. 308 | if conflicts == nil { 309 | tinfo.fields = append(tinfo.fields, *newf) 310 | return nil 311 | } 312 | 313 | // If any conflict is shallower, ignore the new field. 314 | // This matches the Go field resolution on embedding. 315 | for _, i := range conflicts { 316 | if len(tinfo.fields[i].idx) < len(newf.idx) { 317 | return nil 318 | } 319 | } 320 | 321 | // Otherwise, if any of them is at the same depth level, it's an error. 322 | for _, i := range conflicts { 323 | oldf := &tinfo.fields[i] 324 | if len(oldf.idx) == len(newf.idx) { 325 | f1 := typ.FieldByIndex(oldf.idx) 326 | f2 := typ.FieldByIndex(newf.idx) 327 | return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")} 328 | } 329 | } 330 | 331 | // Otherwise, the new field is shallower, and thus takes precedence, 332 | // so drop the conflicting fields from tinfo and append the new one. 333 | for c := len(conflicts) - 1; c >= 0; c-- { 334 | i := conflicts[c] 335 | copy(tinfo.fields[i:], tinfo.fields[i+1:]) 336 | tinfo.fields = tinfo.fields[:len(tinfo.fields)-1] 337 | } 338 | tinfo.fields = append(tinfo.fields, *newf) 339 | return nil 340 | } 341 | 342 | // A TagPathError represents an error in the unmarshalling process 343 | // caused by the use of field tags with conflicting paths. 344 | type TagPathError struct { 345 | Struct reflect.Type 346 | Field1, Tag1 string 347 | Field2, Tag2 string 348 | } 349 | 350 | func (e *TagPathError) Error() string { 351 | 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) 352 | } 353 | 354 | // value returns v's field value corresponding to finfo. 355 | // It's equivalent to v.FieldByIndex(finfo.idx), but initializes 356 | // and dereferences pointers as necessary. 357 | func (finfo *fieldInfo) value(v reflect.Value) reflect.Value { 358 | for i, x := range finfo.idx { 359 | if i > 0 { 360 | t := v.Type() 361 | if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { 362 | if v.IsNil() { 363 | v.Set(reflect.New(v.Type().Elem())) 364 | } 365 | v = v.Elem() 366 | } 367 | } 368 | v = v.Field(x) 369 | } 370 | return v 371 | } 372 | -------------------------------------------------------------------------------- /webdav/lock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package webdav 6 | 7 | import ( 8 | "container/heap" 9 | "errors" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | var ( 17 | // ErrConfirmationFailed is returned by a LockSystem's Confirm method. 18 | ErrConfirmationFailed = errors.New("webdav: confirmation failed") 19 | // ErrForbidden is returned by a LockSystem's Unlock method. 20 | ErrForbidden = errors.New("webdav: forbidden") 21 | // ErrLocked is returned by a LockSystem's Create, Refresh and Unlock methods. 22 | ErrLocked = errors.New("webdav: locked") 23 | // ErrNoSuchLock is returned by a LockSystem's Refresh and Unlock methods. 24 | ErrNoSuchLock = errors.New("webdav: no such lock") 25 | ) 26 | 27 | // Condition can match a WebDAV resource, based on a token or ETag. 28 | // Exactly one of Token and ETag should be non-empty. 29 | type Condition struct { 30 | Not bool 31 | Token string 32 | ETag string 33 | } 34 | 35 | // LockSystem manages access to a collection of named resources. The elements 36 | // in a lock name are separated by slash ('/', U+002F) characters, regardless 37 | // of host operating system convention. 38 | type LockSystem interface { 39 | // Confirm confirms that the caller can claim all of the locks specified by 40 | // the given conditions, and that holding the union of all of those locks 41 | // gives exclusive access to all of the named resources. Up to two resources 42 | // can be named. Empty names are ignored. 43 | // 44 | // Exactly one of release and err will be non-nil. If release is non-nil, 45 | // all of the requested locks are held until release is called. Calling 46 | // release does not unlock the lock, in the WebDAV UNLOCK sense, but once 47 | // Confirm has confirmed that a lock claim is valid, that lock cannot be 48 | // Confirmed again until it has been released. 49 | // 50 | // If Confirm returns ErrConfirmationFailed then the Handler will continue 51 | // to try any other set of locks presented (a WebDAV HTTP request can 52 | // present more than one set of locks). If it returns any other non-nil 53 | // error, the Handler will write a "500 Internal Server Error" HTTP status. 54 | Confirm(now time.Time, name0, name1 string, conditions ...Condition) (release func(), err error) 55 | 56 | // Create creates a lock with the given depth, duration, owner and root 57 | // (name). The depth will either be negative (meaning infinite) or zero. 58 | // 59 | // If Create returns ErrLocked then the Handler will write a "423 Locked" 60 | // HTTP status. If it returns any other non-nil error, the Handler will 61 | // write a "500 Internal Server Error" HTTP status. 62 | // 63 | // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for 64 | // when to use each error. 65 | // 66 | // The token returned identifies the created lock. It should be an absolute 67 | // URI as defined by RFC 3986, Section 4.3. In particular, it should not 68 | // contain whitespace. 69 | Create(now time.Time, details LockDetails) (token string, err error) 70 | 71 | // Refresh refreshes the lock with the given token. 72 | // 73 | // If Refresh returns ErrLocked then the Handler will write a "423 Locked" 74 | // HTTP Status. If Refresh returns ErrNoSuchLock then the Handler will write 75 | // a "412 Precondition Failed" HTTP Status. If it returns any other non-nil 76 | // error, the Handler will write a "500 Internal Server Error" HTTP status. 77 | // 78 | // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for 79 | // when to use each error. 80 | Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) 81 | 82 | // Unlock unlocks the lock with the given token. 83 | // 84 | // If Unlock returns ErrForbidden then the Handler will write a "403 85 | // Forbidden" HTTP Status. If Unlock returns ErrLocked then the Handler 86 | // will write a "423 Locked" HTTP status. If Unlock returns ErrNoSuchLock 87 | // then the Handler will write a "409 Conflict" HTTP Status. If it returns 88 | // any other non-nil error, the Handler will write a "500 Internal Server 89 | // Error" HTTP status. 90 | // 91 | // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.11.1 for 92 | // when to use each error. 93 | Unlock(now time.Time, token string) error 94 | } 95 | 96 | // LockDetails are a lock's metadata. 97 | type LockDetails struct { 98 | // Root is the root resource name being locked. For a zero-depth lock, the 99 | // root is the only resource being locked. 100 | Root string 101 | // Duration is the lock timeout. A negative duration means infinite. 102 | Duration time.Duration 103 | // OwnerXML is the verbatim XML given in a LOCK HTTP request. 104 | // 105 | // TODO: does the "verbatim" nature play well with XML namespaces? 106 | // Does the OwnerXML field need to have more structure? See 107 | // https://codereview.appspot.com/175140043/#msg2 108 | OwnerXML string 109 | // ZeroDepth is whether the lock has zero depth. If it does not have zero 110 | // depth, it has infinite depth. 111 | ZeroDepth bool 112 | } 113 | 114 | // NewMemLS returns a new in-memory LockSystem. 115 | func NewMemLS() LockSystem { 116 | return &memLS{ 117 | byName: make(map[string]*memLSNode), 118 | byToken: make(map[string]*memLSNode), 119 | gen: uint64(time.Now().Unix()), 120 | } 121 | } 122 | 123 | type memLS struct { 124 | mu sync.Mutex 125 | byName map[string]*memLSNode 126 | byToken map[string]*memLSNode 127 | gen uint64 128 | // byExpiry only contains those nodes whose LockDetails have a finite 129 | // Duration and are yet to expire. 130 | byExpiry byExpiry 131 | } 132 | 133 | func (m *memLS) nextToken() string { 134 | m.gen++ 135 | return strconv.FormatUint(m.gen, 10) 136 | } 137 | 138 | func (m *memLS) collectExpiredNodes(now time.Time) { 139 | for len(m.byExpiry) > 0 { 140 | if now.Before(m.byExpiry[0].expiry) { 141 | break 142 | } 143 | m.remove(m.byExpiry[0]) 144 | } 145 | } 146 | 147 | func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condition) (func(), error) { 148 | m.mu.Lock() 149 | defer m.mu.Unlock() 150 | m.collectExpiredNodes(now) 151 | 152 | var n0, n1 *memLSNode 153 | if name0 != "" { 154 | if n0 = m.lookup(slashClean(name0), conditions...); n0 == nil { 155 | return nil, ErrConfirmationFailed 156 | } 157 | } 158 | if name1 != "" { 159 | if n1 = m.lookup(slashClean(name1), conditions...); n1 == nil { 160 | return nil, ErrConfirmationFailed 161 | } 162 | } 163 | 164 | // Don't hold the same node twice. 165 | if n1 == n0 { 166 | n1 = nil 167 | } 168 | 169 | if n0 != nil { 170 | m.hold(n0) 171 | } 172 | if n1 != nil { 173 | m.hold(n1) 174 | } 175 | return func() { 176 | m.mu.Lock() 177 | defer m.mu.Unlock() 178 | if n1 != nil { 179 | m.unhold(n1) 180 | } 181 | if n0 != nil { 182 | m.unhold(n0) 183 | } 184 | }, nil 185 | } 186 | 187 | // lookup returns the node n that locks the named resource, provided that n 188 | // matches at least one of the given conditions and that lock isn't held by 189 | // another party. Otherwise, it returns nil. 190 | // 191 | // n may be a parent of the named resource, if n is an infinite depth lock. 192 | func (m *memLS) lookup(name string, conditions ...Condition) (n *memLSNode) { 193 | // TODO: support Condition.Not and Condition.ETag. 194 | for _, c := range conditions { 195 | n = m.byToken[c.Token] 196 | if n == nil || n.held { 197 | continue 198 | } 199 | if name == n.details.Root { 200 | return n 201 | } 202 | if n.details.ZeroDepth { 203 | continue 204 | } 205 | if n.details.Root == "/" || strings.HasPrefix(name, n.details.Root+"/") { 206 | return n 207 | } 208 | } 209 | return nil 210 | } 211 | 212 | func (m *memLS) hold(n *memLSNode) { 213 | if n.held { 214 | panic("webdav: memLS inconsistent held state") 215 | } 216 | n.held = true 217 | if n.details.Duration >= 0 && n.byExpiryIndex >= 0 { 218 | heap.Remove(&m.byExpiry, n.byExpiryIndex) 219 | } 220 | } 221 | 222 | func (m *memLS) unhold(n *memLSNode) { 223 | if !n.held { 224 | panic("webdav: memLS inconsistent held state") 225 | } 226 | n.held = false 227 | if n.details.Duration >= 0 { 228 | heap.Push(&m.byExpiry, n) 229 | } 230 | } 231 | 232 | func (m *memLS) Create(now time.Time, details LockDetails) (string, error) { 233 | m.mu.Lock() 234 | defer m.mu.Unlock() 235 | m.collectExpiredNodes(now) 236 | details.Root = slashClean(details.Root) 237 | 238 | if !m.canCreate(details.Root, details.ZeroDepth) { 239 | return "", ErrLocked 240 | } 241 | n := m.create(details.Root) 242 | n.token = m.nextToken() 243 | m.byToken[n.token] = n 244 | n.details = details 245 | if n.details.Duration >= 0 { 246 | n.expiry = now.Add(n.details.Duration) 247 | heap.Push(&m.byExpiry, n) 248 | } 249 | return n.token, nil 250 | } 251 | 252 | func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) { 253 | m.mu.Lock() 254 | defer m.mu.Unlock() 255 | m.collectExpiredNodes(now) 256 | 257 | n := m.byToken[token] 258 | if n == nil { 259 | return LockDetails{}, ErrNoSuchLock 260 | } 261 | if n.held { 262 | return LockDetails{}, ErrLocked 263 | } 264 | if n.byExpiryIndex >= 0 { 265 | heap.Remove(&m.byExpiry, n.byExpiryIndex) 266 | } 267 | n.details.Duration = duration 268 | if n.details.Duration >= 0 { 269 | n.expiry = now.Add(n.details.Duration) 270 | heap.Push(&m.byExpiry, n) 271 | } 272 | return n.details, nil 273 | } 274 | 275 | func (m *memLS) Unlock(now time.Time, token string) error { 276 | m.mu.Lock() 277 | defer m.mu.Unlock() 278 | m.collectExpiredNodes(now) 279 | 280 | n := m.byToken[token] 281 | if n == nil { 282 | return ErrNoSuchLock 283 | } 284 | if n.held { 285 | return ErrLocked 286 | } 287 | m.remove(n) 288 | return nil 289 | } 290 | 291 | func (m *memLS) canCreate(name string, zeroDepth bool) bool { 292 | return walkToRoot(name, func(name0 string, first bool) bool { 293 | n := m.byName[name0] 294 | if n == nil { 295 | return true 296 | } 297 | if first { 298 | if n.token != "" { 299 | // The target node is already locked. 300 | return false 301 | } 302 | if !zeroDepth { 303 | // The requested lock depth is infinite, and the fact that n exists 304 | // (n != nil) means that a descendent of the target node is locked. 305 | return false 306 | } 307 | } else if n.token != "" && !n.details.ZeroDepth { 308 | // An ancestor of the target node is locked with infinite depth. 309 | return false 310 | } 311 | return true 312 | }) 313 | } 314 | 315 | func (m *memLS) create(name string) (ret *memLSNode) { 316 | walkToRoot(name, func(name0 string, first bool) bool { 317 | n := m.byName[name0] 318 | if n == nil { 319 | n = &memLSNode{ 320 | details: LockDetails{ 321 | Root: name0, 322 | }, 323 | byExpiryIndex: -1, 324 | } 325 | m.byName[name0] = n 326 | } 327 | n.refCount++ 328 | if first { 329 | ret = n 330 | } 331 | return true 332 | }) 333 | return ret 334 | } 335 | 336 | func (m *memLS) remove(n *memLSNode) { 337 | delete(m.byToken, n.token) 338 | n.token = "" 339 | walkToRoot(n.details.Root, func(name0 string, first bool) bool { 340 | x := m.byName[name0] 341 | x.refCount-- 342 | if x.refCount == 0 { 343 | delete(m.byName, name0) 344 | } 345 | return true 346 | }) 347 | if n.byExpiryIndex >= 0 { 348 | heap.Remove(&m.byExpiry, n.byExpiryIndex) 349 | } 350 | } 351 | 352 | func walkToRoot(name string, f func(name0 string, first bool) bool) bool { 353 | for first := true; ; first = false { 354 | if !f(name, first) { 355 | return false 356 | } 357 | if name == "/" { 358 | break 359 | } 360 | name = name[:strings.LastIndex(name, "/")] 361 | if name == "" { 362 | name = "/" 363 | } 364 | } 365 | return true 366 | } 367 | 368 | type memLSNode struct { 369 | // details are the lock metadata. Even if this node's name is not explicitly locked, 370 | // details.Root will still equal the node's name. 371 | details LockDetails 372 | // token is the unique identifier for this node's lock. An empty token means that 373 | // this node is not explicitly locked. 374 | token string 375 | // refCount is the number of self-or-descendent nodes that are explicitly locked. 376 | refCount int 377 | // expiry is when this node's lock expires. 378 | expiry time.Time 379 | // byExpiryIndex is the index of this node in memLS.byExpiry. It is -1 380 | // if this node does not expire, or has expired. 381 | byExpiryIndex int 382 | // held is whether this node's lock is actively held by a Confirm call. 383 | held bool 384 | } 385 | 386 | type byExpiry []*memLSNode 387 | 388 | func (b *byExpiry) Len() int { 389 | return len(*b) 390 | } 391 | 392 | func (b *byExpiry) Less(i, j int) bool { 393 | return (*b)[i].expiry.Before((*b)[j].expiry) 394 | } 395 | 396 | func (b *byExpiry) Swap(i, j int) { 397 | (*b)[i], (*b)[j] = (*b)[j], (*b)[i] 398 | (*b)[i].byExpiryIndex = i 399 | (*b)[j].byExpiryIndex = j 400 | } 401 | 402 | func (b *byExpiry) Push(x interface{}) { 403 | n := x.(*memLSNode) 404 | n.byExpiryIndex = len(*b) 405 | *b = append(*b, n) 406 | } 407 | 408 | func (b *byExpiry) Pop() interface{} { 409 | i := len(*b) - 1 410 | n := (*b)[i] 411 | (*b)[i] = nil 412 | n.byExpiryIndex = -1 413 | *b = (*b)[:i] 414 | return n 415 | } 416 | 417 | const infiniteTimeout = -1 418 | 419 | // parseTimeout parses the Timeout HTTP header, as per section 10.7. If s is 420 | // empty, an infiniteTimeout is returned. 421 | func parseTimeout(s string) (time.Duration, error) { 422 | if s == "" { 423 | return infiniteTimeout, nil 424 | } 425 | if i := strings.IndexByte(s, ','); i >= 0 { 426 | s = s[:i] 427 | } 428 | s = strings.TrimSpace(s) 429 | if s == "Infinite" { 430 | return infiniteTimeout, nil 431 | } 432 | const pre = "Second-" 433 | if !strings.HasPrefix(s, pre) { 434 | return 0, errInvalidTimeout 435 | } 436 | s = s[len(pre):] 437 | if s == "" || s[0] < '0' || '9' < s[0] { 438 | return 0, errInvalidTimeout 439 | } 440 | n, err := strconv.ParseInt(s, 10, 64) 441 | if err != nil || 1<<32-1 < n { 442 | return 0, errInvalidTimeout 443 | } 444 | return time.Duration(n) * time.Second, nil 445 | } 446 | -------------------------------------------------------------------------------- /webdav/prop.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package webdav 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "encoding/xml" 11 | "errors" 12 | "fmt" 13 | "mime" 14 | "net/http" 15 | "path/filepath" 16 | "strconv" 17 | 18 | "github.com/gaoyb7/115drive-webdav/common/drive" 19 | ) 20 | 21 | // Proppatch describes a property update instruction as defined in RFC 4918. 22 | // See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH 23 | type Proppatch struct { 24 | // Remove specifies whether this patch removes properties. If it does not 25 | // remove them, it sets them. 26 | Remove bool 27 | // Props contains the properties to be set or removed. 28 | Props []Property 29 | } 30 | 31 | // Propstat describes a XML propstat element as defined in RFC 4918. 32 | // See http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat 33 | type Propstat struct { 34 | // Props contains the properties for which Status applies. 35 | Props []Property 36 | 37 | // Status defines the HTTP status code of the properties in Prop. 38 | // Allowed values include, but are not limited to the WebDAV status 39 | // code extensions for HTTP/1.1. 40 | // http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11 41 | Status int 42 | 43 | // XMLError contains the XML representation of the optional error element. 44 | // XML content within this field must not rely on any predefined 45 | // namespace declarations or prefixes. If empty, the XML error element 46 | // is omitted. 47 | XMLError string 48 | 49 | // ResponseDescription contains the contents of the optional 50 | // responsedescription field. If empty, the XML element is omitted. 51 | ResponseDescription string 52 | } 53 | 54 | // makePropstats returns a slice containing those of x and y whose Props slice 55 | // is non-empty. If both are empty, it returns a slice containing an otherwise 56 | // zero Propstat whose HTTP status code is 200 OK. 57 | func makePropstats(x, y Propstat) []Propstat { 58 | pstats := make([]Propstat, 0, 2) 59 | if len(x.Props) != 0 { 60 | pstats = append(pstats, x) 61 | } 62 | if len(y.Props) != 0 { 63 | pstats = append(pstats, y) 64 | } 65 | if len(pstats) == 0 { 66 | pstats = append(pstats, Propstat{ 67 | Status: http.StatusOK, 68 | }) 69 | } 70 | return pstats 71 | } 72 | 73 | // DeadPropsHolder holds the dead properties of a resource. 74 | // 75 | // Dead properties are those properties that are explicitly defined. In 76 | // comparison, live properties, such as DAV:getcontentlength, are implicitly 77 | // defined by the underlying resource, and cannot be explicitly overridden or 78 | // removed. See the Terminology section of 79 | // http://www.webdav.org/specs/rfc4918.html#rfc.section.3 80 | // 81 | // There is a whitelist of the names of live properties. This package handles 82 | // all live properties, and will only pass non-whitelisted names to the Patch 83 | // method of DeadPropsHolder implementations. 84 | type DeadPropsHolder interface { 85 | // DeadProps returns a copy of the dead properties held. 86 | DeadProps() (map[xml.Name]Property, error) 87 | 88 | // Patch patches the dead properties held. 89 | // 90 | // Patching is atomic; either all or no patches succeed. It returns (nil, 91 | // non-nil) if an internal server error occurred, otherwise the Propstats 92 | // collectively contain one Property for each proposed patch Property. If 93 | // all patches succeed, Patch returns a slice of length one and a Propstat 94 | // element with a 200 OK HTTP status code. If none succeed, for reasons 95 | // other than an internal server error, no Propstat has status 200 OK. 96 | // 97 | // For more details on when various HTTP status codes apply, see 98 | // http://www.webdav.org/specs/rfc4918.html#PROPPATCH-status 99 | Patch([]Proppatch) ([]Propstat, error) 100 | } 101 | 102 | // liveProps contains all supported, protected DAV: properties. 103 | var liveProps = map[xml.Name]struct { 104 | // findFn implements the propfind function of this property. If nil, 105 | // it indicates a hidden property. 106 | findFn func(context.Context, string, drive.File) (string, error) 107 | // dir is true if the property applies to directories. 108 | dir bool 109 | }{ 110 | {Space: "DAV:", Local: "resourcetype"}: { 111 | findFn: findResourceType, 112 | dir: true, 113 | }, 114 | {Space: "DAV:", Local: "displayname"}: { 115 | findFn: findDisplayName, 116 | dir: true, 117 | }, 118 | {Space: "DAV:", Local: "getcontentlength"}: { 119 | findFn: findContentLength, 120 | dir: false, 121 | }, 122 | {Space: "DAV:", Local: "getlastmodified"}: { 123 | findFn: findLastModified, 124 | // http://webdav.org/specs/rfc4918.html#PROPERTY_getlastmodified 125 | // suggests that getlastmodified should only apply to GETable 126 | // resources, and this package does not support GET on directories. 127 | // 128 | // Nonetheless, some WebDAV clients expect child directories to be 129 | // sortable by getlastmodified date, so this value is true, not false. 130 | // See golang.org/issue/15334. 131 | dir: true, 132 | }, 133 | {Space: "DAV:", Local: "creationdate"}: { 134 | findFn: nil, 135 | dir: false, 136 | }, 137 | {Space: "DAV:", Local: "getcontentlanguage"}: { 138 | findFn: nil, 139 | dir: false, 140 | }, 141 | {Space: "DAV:", Local: "getcontenttype"}: { 142 | findFn: findContentType, 143 | dir: false, 144 | }, 145 | {Space: "DAV:", Local: "getetag"}: { 146 | findFn: findETag, 147 | // findETag implements ETag as the concatenated hex values of a file's 148 | // modification time and size. This is not a reliable synchronization 149 | // mechanism for directories, so we do not advertise getetag for DAV 150 | // collections. 151 | dir: false, 152 | }, 153 | 154 | // TODO: The lockdiscovery property requires LockSystem to list the 155 | // active locks on a resource. 156 | {Space: "DAV:", Local: "lockdiscovery"}: {}, 157 | {Space: "DAV:", Local: "supportedlock"}: { 158 | findFn: findSupportedLock, 159 | dir: true, 160 | }, 161 | } 162 | 163 | // TODO(nigeltao) merge props and allprop? 164 | 165 | // Props returns the status of the properties named pnames for resource name. 166 | // 167 | // Each Propstat has a unique status and each property name will only be part 168 | // of one Propstat element. 169 | func props(ctx context.Context, fi drive.File, pnames []xml.Name) ([]Propstat, error) { 170 | var deadProps map[xml.Name]Property 171 | isDir := fi.IsDir() 172 | pstatOK := Propstat{Status: http.StatusOK} 173 | pstatNotFound := Propstat{Status: http.StatusNotFound} 174 | for _, pn := range pnames { 175 | // If this file has dead properties, check if they contain pn. 176 | if dp, ok := deadProps[pn]; ok { 177 | pstatOK.Props = append(pstatOK.Props, dp) 178 | continue 179 | } 180 | // Otherwise, it must either be a live property or we don't know it. 181 | if prop := liveProps[pn]; prop.findFn != nil && (prop.dir || !isDir) { 182 | innerXML, err := prop.findFn(ctx, fi.GetName(), fi) 183 | if err != nil { 184 | return nil, err 185 | } 186 | pstatOK.Props = append(pstatOK.Props, Property{ 187 | XMLName: pn, 188 | InnerXML: []byte(innerXML), 189 | }) 190 | } else { 191 | pstatNotFound.Props = append(pstatNotFound.Props, Property{ 192 | XMLName: pn, 193 | }) 194 | } 195 | } 196 | return makePropstats(pstatOK, pstatNotFound), nil 197 | } 198 | 199 | // Propnames returns the property names defined for resource name. 200 | func propnames(ctx context.Context, fi drive.File) ([]xml.Name, error) { 201 | var deadProps map[xml.Name]Property 202 | isDir := fi.IsDir() 203 | pnames := make([]xml.Name, 0, len(liveProps)+len(deadProps)) 204 | for pn, prop := range liveProps { 205 | if prop.findFn != nil && (prop.dir || !isDir) { 206 | pnames = append(pnames, pn) 207 | } 208 | } 209 | for pn := range deadProps { 210 | pnames = append(pnames, pn) 211 | } 212 | return pnames, nil 213 | } 214 | 215 | // Allprop returns the properties defined for resource name and the properties 216 | // named in include. 217 | // 218 | // Note that RFC 4918 defines 'allprop' to return the DAV: properties defined 219 | // within the RFC plus dead properties. Other live properties should only be 220 | // returned if they are named in 'include'. 221 | // 222 | // See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND 223 | func allprop(ctx context.Context, fi drive.File, include []xml.Name) ([]Propstat, error) { 224 | pnames, err := propnames(ctx, fi) 225 | if err != nil { 226 | return nil, err 227 | } 228 | // Add names from include if they are not already covered in pnames. 229 | nameset := make(map[xml.Name]bool) 230 | for _, pn := range pnames { 231 | nameset[pn] = true 232 | } 233 | for _, pn := range include { 234 | if !nameset[pn] { 235 | pnames = append(pnames, pn) 236 | } 237 | } 238 | return props(ctx, fi, pnames) 239 | } 240 | 241 | func escapeXML(s string) string { 242 | for i := 0; i < len(s); i++ { 243 | // As an optimization, if s contains only ASCII letters, digits or a 244 | // few special characters, the escaped value is s itself and we don't 245 | // need to allocate a buffer and convert between string and []byte. 246 | switch c := s[i]; { 247 | case c == ' ' || c == '_' || 248 | ('+' <= c && c <= '9') || // Digits as well as + , - . and / 249 | ('A' <= c && c <= 'Z') || 250 | ('a' <= c && c <= 'z'): 251 | continue 252 | } 253 | // Otherwise, go through the full escaping process. 254 | var buf bytes.Buffer 255 | xml.EscapeText(&buf, []byte(s)) 256 | return buf.String() 257 | } 258 | return s 259 | } 260 | 261 | func findResourceType(ctx context.Context, name string, fi drive.File) (string, error) { 262 | if fi.IsDir() { 263 | return ``, nil 264 | } 265 | return "", nil 266 | } 267 | 268 | func findDisplayName(ctx context.Context, name string, fi drive.File) (string, error) { 269 | if slashClean(name) == "/" { 270 | // Hide the real name of a possibly prefixed root directory. 271 | return "", nil 272 | } 273 | return escapeXML(fi.GetName()), nil 274 | } 275 | 276 | func findContentLength(ctx context.Context, name string, fi drive.File) (string, error) { 277 | return strconv.FormatInt(fi.GetSize(), 10), nil 278 | } 279 | 280 | func findLastModified(ctx context.Context, name string, fi drive.File) (string, error) { 281 | return fi.GetUpdateTime().Format(http.TimeFormat), nil 282 | } 283 | 284 | // ErrNotImplemented should be returned by optional interfaces if they 285 | // want the original implementation to be used. 286 | var ErrNotImplemented = errors.New("not implemented") 287 | 288 | // ContentTyper is an optional interface for the os.FileInfo 289 | // objects returned by the FileSystem. 290 | // 291 | // If this interface is defined then it will be used to read the 292 | // content type from the object. 293 | // 294 | // If this interface is not defined the file will be opened and the 295 | // content type will be guessed from the initial contents of the file. 296 | type ContentTyper interface { 297 | // ContentType returns the content type for the file. 298 | // 299 | // If this returns error ErrNotImplemented then the error will 300 | // be ignored and the base implementation will be used 301 | // instead. 302 | ContentType(ctx context.Context) (string, error) 303 | } 304 | 305 | func findContentType(ctx context.Context, name string, fi drive.File) (string, error) { 306 | ctype := mime.TypeByExtension(filepath.Ext(name)) 307 | if ctype != "" { 308 | return ctype, nil 309 | } 310 | return "application/octet-stream", nil 311 | } 312 | 313 | // ETager is an optional interface for the os.FileInfo objects 314 | // returned by the FileSystem. 315 | // 316 | // If this interface is defined then it will be used to read the ETag 317 | // for the object. 318 | // 319 | // If this interface is not defined an ETag will be computed using the 320 | // ModTime() and the Size() methods of the os.FileInfo object. 321 | type ETager interface { 322 | // ETag returns an ETag for the file. This should be of the 323 | // form "value" or W/"value" 324 | // 325 | // If this returns error ErrNotImplemented then the error will 326 | // be ignored and the base implementation will be used 327 | // instead. 328 | ETag(ctx context.Context) (string, error) 329 | } 330 | 331 | func findETag(ctx context.Context, name string, fi drive.File) (string, error) { 332 | // The Apache http 2.4 web server by default concatenates the 333 | // modification time and size of a file. We replicate the heuristic 334 | // with nanosecond granularity. 335 | return fmt.Sprintf(`"%x%x"`, fi.GetUpdateTime().UnixNano(), fi.GetSize()), nil 336 | } 337 | 338 | func findSupportedLock(ctx context.Context, name string, fi drive.File) (string, error) { 339 | return `` + 340 | `` + 341 | `` + 342 | `` + 343 | ``, nil 344 | } 345 | -------------------------------------------------------------------------------- /webdav/webdav.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package webdav provides a WebDAV server implementation. 6 | package webdav // import "golang.org/x/net/webdav" 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "net/http" 12 | "net/url" 13 | "path" 14 | "strings" 15 | "time" 16 | 17 | "github.com/gaoyb7/115drive-webdav/common" 18 | "github.com/gaoyb7/115drive-webdav/common/drive" 19 | "github.com/sirupsen/logrus" 20 | ) 21 | 22 | type Handler struct { 23 | // Prefix is the URL path prefix to strip from WebDAV resource paths. 24 | Prefix string 25 | // DriveClient is 115 drive client. 26 | DriveClient drive.DriveClient 27 | // LockSystem is the lock management system. 28 | LockSystem LockSystem 29 | // Logger is an optional error logger. If non-nil, it will be called 30 | // for all HTTP requests. 31 | Logger func(*http.Request, error) 32 | } 33 | 34 | func (h *Handler) stripPrefix(p string) (string, int, error) { 35 | if h.Prefix == "" { 36 | return p, http.StatusOK, nil 37 | } 38 | if r := strings.TrimPrefix(p, h.Prefix); len(r) < len(p) { 39 | return r, http.StatusOK, nil 40 | } 41 | return p, http.StatusNotFound, errPrefixMismatch 42 | } 43 | 44 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 45 | status, err := http.StatusBadRequest, errUnsupportedMethod 46 | switch r.Method { 47 | case "OPTIONS": 48 | status, err = h.handleOptions(w, r) 49 | case "GET", "HEAD", "POST": 50 | status, err = h.handleGetHeadPost(w, r) 51 | case "DELETE": 52 | status, err = h.handleDelete(w, r) 53 | case "PUT": 54 | status, err = http.StatusMethodNotAllowed, errUnsupportedMethod 55 | case "MKCOL": 56 | status, err = h.handleMkcol(w, r) 57 | case "MOVE": 58 | status, err = h.handleMove(w, r) 59 | case "LOCK": 60 | status, err = http.StatusMethodNotAllowed, errUnsupportedMethod 61 | case "UNLOCK": 62 | status, err = http.StatusMethodNotAllowed, errUnsupportedMethod 63 | case "PROPFIND": 64 | status, err = h.handlePropfind(w, r) 65 | case "PROPPATCH": 66 | status, err = http.StatusMethodNotAllowed, errUnsupportedMethod 67 | } 68 | 69 | if status != 0 { 70 | w.WriteHeader(status) 71 | if status != http.StatusNoContent { 72 | w.Write([]byte(StatusText(status))) 73 | } 74 | } 75 | if h.Logger != nil { 76 | h.Logger(r, err) 77 | } 78 | } 79 | 80 | func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status int, err error) { 81 | reqPath, status, err := h.stripPrefix(r.URL.Path) 82 | if err != nil { 83 | return status, err 84 | } 85 | // allow := "OPTIONS, LOCK, PUT, MKCOL" 86 | allow := "OPTIONS" 87 | if fi, err := h.DriveClient.GetFile(reqPath); err == nil { 88 | if fi.IsDir() { 89 | // allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND" 90 | allow = "OPTIONS, DELETE, MOVE, PROPFIND" 91 | } else { 92 | // allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT" 93 | allow = "OPTIONS, GET, HEAD, POST, DELETE, MOVE, PROPFIND" 94 | } 95 | } else { 96 | if !errors.Is(err, common.ErrNotFound) { 97 | logrus.WithError(err).Errorf("handleOptions, call h.DriveClient.GetFile fail, req_path: %s", reqPath) 98 | } 99 | } 100 | w.Header().Set("Allow", allow) 101 | // http://www.webdav.org/specs/rfc4918.html#dav.compliance.classes 102 | w.Header().Set("DAV", "1, 2") 103 | // http://msdn.microsoft.com/en-au/library/cc250217.aspx 104 | w.Header().Set("MS-Author-Via", "DAV") 105 | return 0, nil 106 | } 107 | 108 | func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) { 109 | reqPath, status, err := h.stripPrefix(r.URL.Path) 110 | if err != nil { 111 | return status, err 112 | } 113 | 114 | fi, err := h.DriveClient.GetFile(reqPath) 115 | if err != nil { 116 | logrus.WithError(err).Errorf("handleGetHeadPost, call h.DriveClient.GetFile fail, req_path: %v", reqPath) 117 | return http.StatusNotFound, err 118 | } 119 | if fi.IsDir() { 120 | return http.StatusMethodNotAllowed, nil 121 | } 122 | 123 | etag, err := findETag(r.Context(), reqPath, fi) 124 | if err != nil { 125 | return http.StatusInternalServerError, err 126 | } 127 | w.Header().Set("ETag", etag) 128 | 129 | h.DriveClient.ServeContent(w, r, fi) 130 | return 0, nil 131 | } 132 | 133 | func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status int, err error) { 134 | reqPath, status, err := h.stripPrefix(r.URL.Path) 135 | if err != nil { 136 | return status, err 137 | } 138 | release, status, err := h.confirmLocks(r, reqPath, "") 139 | if err != nil { 140 | return status, err 141 | } 142 | defer release() 143 | 144 | // TODO: return MultiStatus where appropriate. 145 | 146 | // "godoc os RemoveAll" says that "If the path does not exist, RemoveAll 147 | // returns nil (no error)." WebDAV semantics are that it should return a 148 | // "404 Not Found". We therefore have to Stat before we RemoveAll. 149 | if err := h.DriveClient.RemoveFile(reqPath); err != nil { 150 | if errors.Is(err, common.ErrNotFound) { 151 | return http.StatusNotFound, err 152 | } 153 | return http.StatusMethodNotAllowed, err 154 | } 155 | return http.StatusNoContent, nil 156 | } 157 | 158 | func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status int, err error) { 159 | reqPath, status, err := h.stripPrefix(r.URL.Path) 160 | if err != nil { 161 | return status, err 162 | } 163 | release, status, err := h.confirmLocks(r, reqPath, "") 164 | if err != nil { 165 | return status, err 166 | } 167 | defer release() 168 | 169 | if r.ContentLength > 0 { 170 | return http.StatusUnsupportedMediaType, nil 171 | } 172 | if err := h.DriveClient.MakeDir(reqPath); err != nil { 173 | if errors.Is(err, common.ErrNotFound) { 174 | return http.StatusNotFound, err 175 | } 176 | return http.StatusMethodNotAllowed, err 177 | } 178 | return http.StatusCreated, nil 179 | } 180 | 181 | func (h *Handler) handleMove(w http.ResponseWriter, r *http.Request) (status int, err error) { 182 | hdr := r.Header.Get("Destination") 183 | if hdr == "" { 184 | return http.StatusBadRequest, errInvalidDestination 185 | } 186 | u, err := url.Parse(hdr) 187 | if err != nil { 188 | return http.StatusBadRequest, errInvalidDestination 189 | } 190 | if u.Host != "" && u.Host != r.Host { 191 | return http.StatusBadGateway, errInvalidDestination 192 | } 193 | 194 | src, status, err := h.stripPrefix(r.URL.Path) 195 | if err != nil { 196 | return status, err 197 | } 198 | 199 | dst, status, err := h.stripPrefix(u.Path) 200 | if err != nil { 201 | return status, err 202 | } 203 | 204 | if dst == "" { 205 | return http.StatusBadGateway, errInvalidDestination 206 | } 207 | if dst == src { 208 | return http.StatusForbidden, errDestinationEqualsSource 209 | } 210 | 211 | release, status, err := h.confirmLocks(r, src, dst) 212 | if err != nil { 213 | return status, err 214 | } 215 | defer release() 216 | 217 | // Section 9.9.2 says that "The MOVE method on a collection must act as if 218 | // a "Depth: infinity" header was used on it. A client must not submit a 219 | // Depth header on a MOVE on a collection with any value but "infinity"." 220 | if hdr := r.Header.Get("Depth"); hdr != "" { 221 | if parseDepth(hdr) != infiniteDepth { 222 | return http.StatusBadRequest, errInvalidDepth 223 | } 224 | } 225 | err = h.DriveClient.MoveFile(src, dst) 226 | if err != nil { 227 | logrus.WithError(err).Errorf("call h.DriveClient.MoveFile fail, src: %s, dst: %s", src, dst) 228 | return http.StatusInternalServerError, err 229 | } 230 | return http.StatusNoContent, nil 231 | 232 | } 233 | 234 | func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status int, err error) { 235 | reqPath, status, err := h.stripPrefix(r.URL.Path) 236 | if err != nil { 237 | return status, err 238 | } 239 | 240 | ctx := r.Context() 241 | fi, err := h.DriveClient.GetFile(reqPath) 242 | if err != nil { 243 | if errors.Is(err, common.ErrNotFound) { 244 | return http.StatusNotFound, err 245 | } 246 | logrus.WithError(err).Errorf("handlePropfind, call h.DriveClient.GetFile fail, req_path: %s", reqPath) 247 | return http.StatusMethodNotAllowed, err 248 | } 249 | depth := infiniteDepth 250 | if hdr := r.Header.Get("Depth"); hdr != "" { 251 | depth = parseDepth(hdr) 252 | if depth == invalidDepth { 253 | return http.StatusBadRequest, errInvalidDepth 254 | } 255 | } 256 | pf, status, err := readPropfind(r.Body) 257 | if err != nil { 258 | return status, err 259 | } 260 | 261 | mw := multistatusWriter{w: w} 262 | 263 | walkFn := func(reqPath string, info drive.File, err error) error { 264 | if err != nil { 265 | return err 266 | } 267 | var pstats []Propstat 268 | if pf.Propname != nil { 269 | pnames, err := propnames(ctx, info) 270 | if err != nil { 271 | return err 272 | } 273 | pstat := Propstat{Status: http.StatusOK} 274 | for _, xmlname := range pnames { 275 | pstat.Props = append(pstat.Props, Property{XMLName: xmlname}) 276 | } 277 | pstats = append(pstats, pstat) 278 | } else if pf.Allprop != nil { 279 | pstats, err = allprop(ctx, info, pf.Prop) 280 | } else { 281 | pstats, err = props(ctx, info, pf.Prop) 282 | } 283 | if err != nil { 284 | return err 285 | } 286 | href := path.Join(h.Prefix, reqPath) 287 | if href != "/" && info.IsDir() { 288 | href += "/" 289 | } 290 | return mw.write(makePropstatResponse(href, pstats)) 291 | } 292 | 293 | walkErr := walkFS(ctx, depth, reqPath, h.DriveClient, fi, walkFn) 294 | closeErr := mw.close() 295 | if walkErr != nil { 296 | return http.StatusInternalServerError, walkErr 297 | } 298 | if closeErr != nil { 299 | return http.StatusInternalServerError, closeErr 300 | } 301 | return 0, nil 302 | } 303 | 304 | func (h *Handler) lock(now time.Time, root string) (token string, status int, err error) { 305 | token, err = h.LockSystem.Create(now, LockDetails{ 306 | Root: root, 307 | Duration: infiniteTimeout, 308 | ZeroDepth: true, 309 | }) 310 | if err != nil { 311 | if err == ErrLocked { 312 | return "", StatusLocked, err 313 | } 314 | return "", http.StatusInternalServerError, err 315 | } 316 | return token, 0, nil 317 | } 318 | 319 | func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func(), status int, err error) { 320 | hdr := r.Header.Get("If") 321 | if hdr == "" { 322 | // An empty If header means that the client hasn't previously created locks. 323 | // Even if this client doesn't care about locks, we still need to check that 324 | // the resources aren't locked by another client, so we create temporary 325 | // locks that would conflict with another client's locks. These temporary 326 | // locks are unlocked at the end of the HTTP request. 327 | now, srcToken, dstToken := time.Now(), "", "" 328 | if src != "" { 329 | srcToken, status, err = h.lock(now, src) 330 | if err != nil { 331 | return nil, status, err 332 | } 333 | } 334 | if dst != "" { 335 | dstToken, status, err = h.lock(now, dst) 336 | if err != nil { 337 | if srcToken != "" { 338 | h.LockSystem.Unlock(now, srcToken) 339 | } 340 | return nil, status, err 341 | } 342 | } 343 | 344 | return func() { 345 | if dstToken != "" { 346 | h.LockSystem.Unlock(now, dstToken) 347 | } 348 | if srcToken != "" { 349 | h.LockSystem.Unlock(now, srcToken) 350 | } 351 | }, 0, nil 352 | } 353 | 354 | ih, ok := parseIfHeader(hdr) 355 | if !ok { 356 | return nil, http.StatusBadRequest, errInvalidIfHeader 357 | } 358 | // ih is a disjunction (OR) of ifLists, so any ifList will do. 359 | for _, l := range ih.lists { 360 | lsrc := l.resourceTag 361 | if lsrc == "" { 362 | lsrc = src 363 | } else { 364 | u, err := url.Parse(lsrc) 365 | if err != nil { 366 | continue 367 | } 368 | if u.Host != r.Host { 369 | continue 370 | } 371 | lsrc, status, err = h.stripPrefix(u.Path) 372 | if err != nil { 373 | return nil, status, err 374 | } 375 | } 376 | release, err = h.LockSystem.Confirm(time.Now(), lsrc, dst, l.conditions...) 377 | if err == ErrConfirmationFailed { 378 | continue 379 | } 380 | if err != nil { 381 | return nil, http.StatusInternalServerError, err 382 | } 383 | return release, 0, nil 384 | } 385 | // Section 10.4.1 says that "If this header is evaluated and all state lists 386 | // fail, then the request must fail with a 412 (Precondition Failed) status." 387 | // We follow the spec even though the cond_put_corrupt_token test case from 388 | // the litmus test warns on seeing a 412 instead of a 423 (Locked). 389 | return nil, http.StatusPreconditionFailed, ErrLocked 390 | } 391 | 392 | func makePropstatResponse(href string, pstats []Propstat) *response { 393 | resp := response{ 394 | Href: []string{(&url.URL{Path: href}).EscapedPath()}, 395 | Propstat: make([]propstat, 0, len(pstats)), 396 | } 397 | for _, p := range pstats { 398 | var xmlErr *xmlError 399 | if p.XMLError != "" { 400 | xmlErr = &xmlError{InnerXML: []byte(p.XMLError)} 401 | } 402 | resp.Propstat = append(resp.Propstat, propstat{ 403 | Status: fmt.Sprintf("HTTP/1.1 %d %s", p.Status, StatusText(p.Status)), 404 | Prop: p.Props, 405 | ResponseDescription: p.ResponseDescription, 406 | Error: xmlErr, 407 | }) 408 | } 409 | return &resp 410 | } 411 | 412 | const ( 413 | infiniteDepth = -1 414 | invalidDepth = -2 415 | ) 416 | 417 | // parseDepth maps the strings "0", "1" and "infinity" to 0, 1 and 418 | // infiniteDepth. Parsing any other string returns invalidDepth. 419 | // 420 | // Different WebDAV methods have further constraints on valid depths: 421 | // - PROPFIND has no further restrictions, as per section 9.1. 422 | // - COPY accepts only "0" or "infinity", as per section 9.8.3. 423 | // - MOVE accepts only "infinity", as per section 9.9.2. 424 | // - LOCK accepts only "0" or "infinity", as per section 9.10.3. 425 | // 426 | // These constraints are enforced by the handleXxx methods. 427 | func parseDepth(s string) int { 428 | switch s { 429 | case "0": 430 | return 0 431 | case "1": 432 | return 1 433 | case "infinity": 434 | return infiniteDepth 435 | } 436 | return invalidDepth 437 | } 438 | 439 | // http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11 440 | const ( 441 | StatusMulti = 207 442 | StatusUnprocessableEntity = 422 443 | StatusLocked = 423 444 | StatusFailedDependency = 424 445 | StatusInsufficientStorage = 507 446 | ) 447 | 448 | func StatusText(code int) string { 449 | switch code { 450 | case StatusMulti: 451 | return "Multi-Status" 452 | case StatusUnprocessableEntity: 453 | return "Unprocessable Entity" 454 | case StatusLocked: 455 | return "Locked" 456 | case StatusFailedDependency: 457 | return "Failed Dependency" 458 | case StatusInsufficientStorage: 459 | return "Insufficient Storage" 460 | } 461 | return http.StatusText(code) 462 | } 463 | 464 | var ( 465 | errDestinationEqualsSource = errors.New("webdav: destination equals source") 466 | errDirectoryNotEmpty = errors.New("webdav: directory not empty") 467 | errInvalidDepth = errors.New("webdav: invalid depth") 468 | errInvalidDestination = errors.New("webdav: invalid destination") 469 | errInvalidIfHeader = errors.New("webdav: invalid If header") 470 | errInvalidLockInfo = errors.New("webdav: invalid lock info") 471 | errInvalidLockToken = errors.New("webdav: invalid lock token") 472 | errInvalidPropfind = errors.New("webdav: invalid propfind") 473 | errInvalidProppatch = errors.New("webdav: invalid proppatch") 474 | errInvalidResponse = errors.New("webdav: invalid response") 475 | errInvalidTimeout = errors.New("webdav: invalid timeout") 476 | errNoFileSystem = errors.New("webdav: no file system") 477 | errNoLockSystem = errors.New("webdav: no lock system") 478 | errNotADirectory = errors.New("webdav: not a directory") 479 | errPrefixMismatch = errors.New("webdav: prefix mismatch") 480 | errRecursionTooDeep = errors.New("webdav: recursion too deep") 481 | errUnsupportedLockInfo = errors.New("webdav: unsupported lock info") 482 | errUnsupportedMethod = errors.New("webdav: unsupported method") 483 | ) 484 | -------------------------------------------------------------------------------- /webdav/xml.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package webdav 6 | 7 | // The XML encoding is covered by Section 14. 8 | // http://www.webdav.org/specs/rfc4918.html#xml.element.definitions 9 | 10 | import ( 11 | "bytes" 12 | "encoding/xml" 13 | "fmt" 14 | "io" 15 | "net/http" 16 | 17 | // As of https://go-review.googlesource.com/#/c/12772/ which was submitted 18 | // in July 2015, this package uses an internal fork of the standard 19 | // library's encoding/xml package, due to changes in the way namespaces 20 | // were encoded. Such changes were introduced in the Go 1.5 cycle, but were 21 | // rolled back in response to https://github.com/golang/go/issues/11841 22 | // 23 | // However, this package's exported API, specifically the Property and 24 | // DeadPropsHolder types, need to refer to the standard library's version 25 | // of the xml.Name type, as code that imports this package cannot refer to 26 | // the internal version. 27 | // 28 | // This file therefore imports both the internal and external versions, as 29 | // ixml and xml, and converts between them. 30 | // 31 | // In the long term, this package should use the standard library's version 32 | // only, and the internal fork deleted, once 33 | // https://github.com/golang/go/issues/13400 is resolved. 34 | ixml "github.com/gaoyb7/115drive-webdav/webdav/internal/xml" 35 | ) 36 | 37 | // http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner 38 | type owner struct { 39 | InnerXML string `xml:",innerxml"` 40 | } 41 | 42 | type countingReader struct { 43 | n int 44 | r io.Reader 45 | } 46 | 47 | func (c *countingReader) Read(p []byte) (int, error) { 48 | n, err := c.r.Read(p) 49 | c.n += n 50 | return n, err 51 | } 52 | 53 | func escape(s string) string { 54 | for i := 0; i < len(s); i++ { 55 | switch s[i] { 56 | case '"', '&', '\'', '<', '>': 57 | b := bytes.NewBuffer(nil) 58 | ixml.EscapeText(b, []byte(s)) 59 | return b.String() 60 | } 61 | } 62 | return s 63 | } 64 | 65 | // Next returns the next token, if any, in the XML stream of d. 66 | // RFC 4918 requires to ignore comments, processing instructions 67 | // and directives. 68 | // http://www.webdav.org/specs/rfc4918.html#property_values 69 | // http://www.webdav.org/specs/rfc4918.html#xml-extensibility 70 | func next(d *ixml.Decoder) (ixml.Token, error) { 71 | for { 72 | t, err := d.Token() 73 | if err != nil { 74 | return t, err 75 | } 76 | switch t.(type) { 77 | case ixml.Comment, ixml.Directive, ixml.ProcInst: 78 | continue 79 | default: 80 | return t, nil 81 | } 82 | } 83 | } 84 | 85 | // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind) 86 | type propfindProps []xml.Name 87 | 88 | // UnmarshalXML appends the property names enclosed within start to pn. 89 | // 90 | // It returns an error if start does not contain any properties or if 91 | // properties contain values. Character data between properties is ignored. 92 | func (pn *propfindProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error { 93 | for { 94 | t, err := next(d) 95 | if err != nil { 96 | return err 97 | } 98 | switch t.(type) { 99 | case ixml.EndElement: 100 | if len(*pn) == 0 { 101 | return fmt.Errorf("%s must not be empty", start.Name.Local) 102 | } 103 | return nil 104 | case ixml.StartElement: 105 | name := t.(ixml.StartElement).Name 106 | t, err = next(d) 107 | if err != nil { 108 | return err 109 | } 110 | if _, ok := t.(ixml.EndElement); !ok { 111 | return fmt.Errorf("unexpected token %T", t) 112 | } 113 | *pn = append(*pn, xml.Name(name)) 114 | } 115 | } 116 | } 117 | 118 | // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind 119 | type propfind struct { 120 | XMLName ixml.Name `xml:"DAV: propfind"` 121 | Allprop *struct{} `xml:"DAV: allprop"` 122 | Propname *struct{} `xml:"DAV: propname"` 123 | Prop propfindProps `xml:"DAV: prop"` 124 | Include propfindProps `xml:"DAV: include"` 125 | } 126 | 127 | func readPropfind(r io.Reader) (pf propfind, status int, err error) { 128 | c := countingReader{r: r} 129 | if err = ixml.NewDecoder(&c).Decode(&pf); err != nil { 130 | if err == io.EOF { 131 | if c.n == 0 { 132 | // An empty body means to propfind allprop. 133 | // http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND 134 | return propfind{Allprop: new(struct{})}, 0, nil 135 | } 136 | err = errInvalidPropfind 137 | } 138 | return propfind{}, http.StatusBadRequest, err 139 | } 140 | 141 | if pf.Allprop == nil && pf.Include != nil { 142 | return propfind{}, http.StatusBadRequest, errInvalidPropfind 143 | } 144 | if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) { 145 | return propfind{}, http.StatusBadRequest, errInvalidPropfind 146 | } 147 | if pf.Prop != nil && pf.Propname != nil { 148 | return propfind{}, http.StatusBadRequest, errInvalidPropfind 149 | } 150 | if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil { 151 | return propfind{}, http.StatusBadRequest, errInvalidPropfind 152 | } 153 | return pf, 0, nil 154 | } 155 | 156 | // Property represents a single DAV resource property as defined in RFC 4918. 157 | // See http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties 158 | type Property struct { 159 | // XMLName is the fully qualified name that identifies this property. 160 | XMLName xml.Name 161 | 162 | // Lang is an optional xml:lang attribute. 163 | Lang string `xml:"xml:lang,attr,omitempty"` 164 | 165 | // InnerXML contains the XML representation of the property value. 166 | // See http://www.webdav.org/specs/rfc4918.html#property_values 167 | // 168 | // Property values of complex type or mixed-content must have fully 169 | // expanded XML namespaces or be self-contained with according 170 | // XML namespace declarations. They must not rely on any XML 171 | // namespace declarations within the scope of the XML document, 172 | // even including the DAV: namespace. 173 | InnerXML []byte `xml:",innerxml"` 174 | } 175 | 176 | // ixmlProperty is the same as the Property type except it holds an ixml.Name 177 | // instead of an xml.Name. 178 | type ixmlProperty struct { 179 | XMLName ixml.Name 180 | Lang string `xml:"xml:lang,attr,omitempty"` 181 | InnerXML []byte `xml:",innerxml"` 182 | } 183 | 184 | // http://www.webdav.org/specs/rfc4918.html#ELEMENT_error 185 | // See multistatusWriter for the "D:" namespace prefix. 186 | type xmlError struct { 187 | XMLName ixml.Name `xml:"D:error"` 188 | InnerXML []byte `xml:",innerxml"` 189 | } 190 | 191 | // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat 192 | // See multistatusWriter for the "D:" namespace prefix. 193 | type propstat struct { 194 | Prop []Property `xml:"D:prop>_ignored_"` 195 | Status string `xml:"D:status"` 196 | Error *xmlError `xml:"D:error"` 197 | ResponseDescription string `xml:"D:responsedescription,omitempty"` 198 | } 199 | 200 | // ixmlPropstat is the same as the propstat type except it holds an ixml.Name 201 | // instead of an xml.Name. 202 | type ixmlPropstat struct { 203 | Prop []ixmlProperty `xml:"D:prop>_ignored_"` 204 | Status string `xml:"D:status"` 205 | Error *xmlError `xml:"D:error"` 206 | ResponseDescription string `xml:"D:responsedescription,omitempty"` 207 | } 208 | 209 | // MarshalXML prepends the "D:" namespace prefix on properties in the DAV: namespace 210 | // before encoding. See multistatusWriter. 211 | func (ps propstat) MarshalXML(e *ixml.Encoder, start ixml.StartElement) error { 212 | // Convert from a propstat to an ixmlPropstat. 213 | ixmlPs := ixmlPropstat{ 214 | Prop: make([]ixmlProperty, len(ps.Prop)), 215 | Status: ps.Status, 216 | Error: ps.Error, 217 | ResponseDescription: ps.ResponseDescription, 218 | } 219 | for k, prop := range ps.Prop { 220 | ixmlPs.Prop[k] = ixmlProperty{ 221 | XMLName: ixml.Name(prop.XMLName), 222 | Lang: prop.Lang, 223 | InnerXML: prop.InnerXML, 224 | } 225 | } 226 | 227 | for k, prop := range ixmlPs.Prop { 228 | if prop.XMLName.Space == "DAV:" { 229 | prop.XMLName = ixml.Name{Space: "", Local: "D:" + prop.XMLName.Local} 230 | ixmlPs.Prop[k] = prop 231 | } 232 | } 233 | // Distinct type to avoid infinite recursion of MarshalXML. 234 | type newpropstat ixmlPropstat 235 | return e.EncodeElement(newpropstat(ixmlPs), start) 236 | } 237 | 238 | // http://www.webdav.org/specs/rfc4918.html#ELEMENT_response 239 | // See multistatusWriter for the "D:" namespace prefix. 240 | type response struct { 241 | XMLName ixml.Name `xml:"D:response"` 242 | Href []string `xml:"D:href"` 243 | Propstat []propstat `xml:"D:propstat"` 244 | Status string `xml:"D:status,omitempty"` 245 | Error *xmlError `xml:"D:error"` 246 | ResponseDescription string `xml:"D:responsedescription,omitempty"` 247 | } 248 | 249 | // MultistatusWriter marshals one or more Responses into a XML 250 | // multistatus response. 251 | // See http://www.webdav.org/specs/rfc4918.html#ELEMENT_multistatus 252 | // TODO(rsto, mpl): As a workaround, the "D:" namespace prefix, defined as 253 | // "DAV:" on this element, is prepended on the nested response, as well as on all 254 | // its nested elements. All property names in the DAV: namespace are prefixed as 255 | // well. This is because some versions of Mini-Redirector (on windows 7) ignore 256 | // elements with a default namespace (no prefixed namespace). A less intrusive fix 257 | // should be possible after golang.org/cl/11074. See https://golang.org/issue/11177 258 | type multistatusWriter struct { 259 | // ResponseDescription contains the optional responsedescription 260 | // of the multistatus XML element. Only the latest content before 261 | // close will be emitted. Empty response descriptions are not 262 | // written. 263 | responseDescription string 264 | 265 | w http.ResponseWriter 266 | enc *ixml.Encoder 267 | } 268 | 269 | // Write validates and emits a DAV response as part of a multistatus response 270 | // element. 271 | // 272 | // It sets the HTTP status code of its underlying http.ResponseWriter to 207 273 | // (Multi-Status) and populates the Content-Type header. If r is the 274 | // first, valid response to be written, Write prepends the XML representation 275 | // of r with a multistatus tag. Callers must call close after the last response 276 | // has been written. 277 | func (w *multistatusWriter) write(r *response) error { 278 | switch len(r.Href) { 279 | case 0: 280 | return errInvalidResponse 281 | case 1: 282 | if len(r.Propstat) > 0 != (r.Status == "") { 283 | return errInvalidResponse 284 | } 285 | default: 286 | if len(r.Propstat) > 0 || r.Status == "" { 287 | return errInvalidResponse 288 | } 289 | } 290 | err := w.writeHeader() 291 | if err != nil { 292 | return err 293 | } 294 | return w.enc.Encode(r) 295 | } 296 | 297 | // writeHeader writes a XML multistatus start element on w's underlying 298 | // http.ResponseWriter and returns the result of the write operation. 299 | // After the first write attempt, writeHeader becomes a no-op. 300 | func (w *multistatusWriter) writeHeader() error { 301 | if w.enc != nil { 302 | return nil 303 | } 304 | w.w.Header().Add("Content-Type", "text/xml; charset=utf-8") 305 | w.w.WriteHeader(StatusMulti) 306 | _, err := fmt.Fprintf(w.w, ``) 307 | if err != nil { 308 | return err 309 | } 310 | w.enc = ixml.NewEncoder(w.w) 311 | return w.enc.EncodeToken(ixml.StartElement{ 312 | Name: ixml.Name{ 313 | Space: "DAV:", 314 | Local: "multistatus", 315 | }, 316 | Attr: []ixml.Attr{{ 317 | Name: ixml.Name{Space: "xmlns", Local: "D"}, 318 | Value: "DAV:", 319 | }}, 320 | }) 321 | } 322 | 323 | // Close completes the marshalling of the multistatus response. It returns 324 | // an error if the multistatus response could not be completed. If both the 325 | // return value and field enc of w are nil, then no multistatus response has 326 | // been written. 327 | func (w *multistatusWriter) close() error { 328 | if w.enc == nil { 329 | return nil 330 | } 331 | var end []ixml.Token 332 | if w.responseDescription != "" { 333 | name := ixml.Name{Space: "DAV:", Local: "responsedescription"} 334 | end = append(end, 335 | ixml.StartElement{Name: name}, 336 | ixml.CharData(w.responseDescription), 337 | ixml.EndElement{Name: name}, 338 | ) 339 | } 340 | end = append(end, ixml.EndElement{ 341 | Name: ixml.Name{Space: "DAV:", Local: "multistatus"}, 342 | }) 343 | for _, t := range end { 344 | err := w.enc.EncodeToken(t) 345 | if err != nil { 346 | return err 347 | } 348 | } 349 | return w.enc.Flush() 350 | } 351 | 352 | var xmlLangName = ixml.Name{Space: "http://www.w3.org/XML/1998/namespace", Local: "lang"} 353 | 354 | func xmlLang(s ixml.StartElement, d string) string { 355 | for _, attr := range s.Attr { 356 | if attr.Name == xmlLangName { 357 | return attr.Value 358 | } 359 | } 360 | return d 361 | } 362 | 363 | type xmlValue []byte 364 | 365 | func (v *xmlValue) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error { 366 | // The XML value of a property can be arbitrary, mixed-content XML. 367 | // To make sure that the unmarshalled value contains all required 368 | // namespaces, we encode all the property value XML tokens into a 369 | // buffer. This forces the encoder to redeclare any used namespaces. 370 | var b bytes.Buffer 371 | e := ixml.NewEncoder(&b) 372 | for { 373 | t, err := next(d) 374 | if err != nil { 375 | return err 376 | } 377 | if e, ok := t.(ixml.EndElement); ok && e.Name == start.Name { 378 | break 379 | } 380 | if err = e.EncodeToken(t); err != nil { 381 | return err 382 | } 383 | } 384 | err := e.Flush() 385 | if err != nil { 386 | return err 387 | } 388 | *v = b.Bytes() 389 | return nil 390 | } 391 | 392 | // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for proppatch) 393 | type proppatchProps []Property 394 | 395 | // UnmarshalXML appends the property names and values enclosed within start 396 | // to ps. 397 | // 398 | // An xml:lang attribute that is defined either on the DAV:prop or property 399 | // name XML element is propagated to the property's Lang field. 400 | // 401 | // UnmarshalXML returns an error if start does not contain any properties or if 402 | // property values contain syntactically incorrect XML. 403 | func (ps *proppatchProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error { 404 | lang := xmlLang(start, "") 405 | for { 406 | t, err := next(d) 407 | if err != nil { 408 | return err 409 | } 410 | switch elem := t.(type) { 411 | case ixml.EndElement: 412 | if len(*ps) == 0 { 413 | return fmt.Errorf("%s must not be empty", start.Name.Local) 414 | } 415 | return nil 416 | case ixml.StartElement: 417 | p := Property{ 418 | XMLName: xml.Name(t.(ixml.StartElement).Name), 419 | Lang: xmlLang(t.(ixml.StartElement), lang), 420 | } 421 | err = d.DecodeElement(((*xmlValue)(&p.InnerXML)), &elem) 422 | if err != nil { 423 | return err 424 | } 425 | *ps = append(*ps, p) 426 | } 427 | } 428 | } 429 | 430 | // http://www.webdav.org/specs/rfc4918.html#ELEMENT_set 431 | // http://www.webdav.org/specs/rfc4918.html#ELEMENT_remove 432 | type setRemove struct { 433 | XMLName ixml.Name 434 | Lang string `xml:"xml:lang,attr,omitempty"` 435 | Prop proppatchProps `xml:"DAV: prop"` 436 | } 437 | 438 | // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propertyupdate 439 | type propertyupdate struct { 440 | XMLName ixml.Name `xml:"DAV: propertyupdate"` 441 | Lang string `xml:"xml:lang,attr,omitempty"` 442 | SetRemove []setRemove `xml:",any"` 443 | } 444 | 445 | func readProppatch(r io.Reader) (patches []Proppatch, status int, err error) { 446 | var pu propertyupdate 447 | if err = ixml.NewDecoder(r).Decode(&pu); err != nil { 448 | return nil, http.StatusBadRequest, err 449 | } 450 | for _, op := range pu.SetRemove { 451 | remove := false 452 | switch op.XMLName { 453 | case ixml.Name{Space: "DAV:", Local: "set"}: 454 | // No-op. 455 | case ixml.Name{Space: "DAV:", Local: "remove"}: 456 | for _, p := range op.Prop { 457 | if len(p.InnerXML) > 0 { 458 | return nil, http.StatusBadRequest, errInvalidProppatch 459 | } 460 | } 461 | remove = true 462 | default: 463 | return nil, http.StatusBadRequest, errInvalidProppatch 464 | } 465 | patches = append(patches, Proppatch{Remove: remove, Props: op.Prop}) 466 | } 467 | return patches, 0, nil 468 | } 469 | --------------------------------------------------------------------------------