github.com/fzfile/BaiduPCS-Go@v0.0.0-20200606205115-4408961cf336/baidupcs/extends.go (about) 1 package baidupcs 2 3 import ( 4 "crypto/md5" 5 "encoding/hex" 6 "errors" 7 "github.com/fzfile/BaiduPCS-Go/baidupcs/pcserror" 8 "github.com/fzfile/BaiduPCS-Go/pcsutil/cachepool" 9 "github.com/fzfile/BaiduPCS-Go/pcsutil/escaper" 10 "github.com/fzfile/BaiduPCS-Go/requester/downloader" 11 "io" 12 "mime" 13 "net/http" 14 "net/url" 15 "path" 16 "strconv" 17 "strings" 18 ) 19 20 const ( 21 // ShellPatternCharacters 通配符字符串 22 ShellPatternCharacters = "*?[]" 23 ) 24 25 var ( 26 // ErrFixMD5Isdir 目录不需要修复md5 27 ErrFixMD5Isdir = errors.New("directory not support fix md5") 28 // ErrFixMD5Failed 修复MD5失败, 可能服务器未刷新 29 ErrFixMD5Failed = errors.New("fix md5 failed") 30 // ErrFixMD5FileInfoNil 文件信息对象为空 31 ErrFixMD5FileInfoNil = errors.New("file info is nil") 32 // ErrMatchPathByShellPatternNotAbsPath 不是绝对路径 33 ErrMatchPathByShellPatternNotAbsPath = errors.New("not absolute path") 34 35 ErrContentRangeNotFound = errors.New("Content-Range not found") 36 ErrGetRapidUploadInfoLengthNotFound = errors.New("Content-Length not found") 37 ErrGetRapidUploadInfoMD5NotFound = errors.New("Content-MD5 not found") 38 ErrGetRapidUploadInfoCrc32NotFound = errors.New("x-bs-meta-crc32 not found") 39 ErrGetRapidUploadInfoFilenameNotEqual = errors.New("文件名不匹配") 40 ErrGetRapidUploadInfoLengthNotEqual = errors.New("Content-Length 不匹配") 41 ErrGetRapidUploadInfoMD5NotEqual = errors.New("Content-MD5 不匹配") 42 ErrGetRapidUploadInfoCrc32NotEqual = errors.New("x-bs-meta-crc32 不匹配") 43 ErrGetRapidUploadInfoSliceMD5NotEqual = errors.New("slice-md5 不匹配") 44 45 ErrFileTooLarge = errors.New("文件大于20GB, 无法秒传") 46 ) 47 48 func (pcs *BaiduPCS) getLocateDownloadLink(pcspath string) (link string, pcsError pcserror.Error) { 49 info, pcsError := pcs.LocateDownload(pcspath) 50 if pcsError != nil { 51 return 52 } 53 54 u := info.SingleURL(pcs.isHTTPS) 55 if u == nil { 56 return "", &pcserror.PCSErrInfo{ 57 Operation: OperationLocateDownload, 58 ErrType: pcserror.ErrTypeOthers, 59 Err: ErrLocateDownloadURLNotFound, 60 } 61 } 62 return u.String(), nil 63 } 64 65 // ExportByFileInfo 通过文件信息对象, 导出文件信息 66 func (pcs *BaiduPCS) ExportByFileInfo(finfo *FileDirectory) (rinfo *RapidUploadInfo, pcsError pcserror.Error) { 67 errInfo := pcserror.NewPCSErrorInfo(OperationExportFileInfo) 68 errInfo.ErrType = pcserror.ErrTypeOthers 69 if finfo.Size > MaxRapidUploadSize { 70 errInfo.Err = ErrFileTooLarge 71 return nil, errInfo 72 } 73 74 rinfo, pcsError = pcs.GetRapidUploadInfoByFileInfo(finfo) 75 if pcsError != nil { 76 return nil, pcsError 77 } 78 if rinfo.Filename != finfo.Filename { 79 baiduPCSVerbose.Infof("%s filename not equal, local: %s, remote link: %s\n", OperationExportFileInfo, finfo.Filename, rinfo.Filename) 80 rinfo.Filename = finfo.Filename 81 } 82 return rinfo, nil 83 } 84 85 // GetRapidUploadInfoByFileInfo 通过文件信息对象, 获取秒传信息 86 func (pcs *BaiduPCS) GetRapidUploadInfoByFileInfo(finfo *FileDirectory) (rinfo *RapidUploadInfo, pcsError pcserror.Error) { 87 if finfo.Size <= SliceMD5Size && len(finfo.BlockList) == 1 && finfo.BlockList[0] == finfo.MD5 { 88 // 可直接秒传 89 return &RapidUploadInfo{ 90 Filename: finfo.Filename, 91 ContentLength: finfo.Size, 92 ContentMD5: finfo.MD5, 93 SliceMD5: finfo.MD5, 94 ContentCrc32: "0", 95 }, nil 96 } 97 98 link, pcsError := pcs.getLocateDownloadLink(finfo.Path) 99 if pcsError != nil { 100 return nil, pcsError 101 } 102 103 // 只有ContentLength可以比较 104 // finfo记录的ContentMD5不一定是正确的 105 // finfo记录的Filename不一定与获取到的一致 106 return pcs.GetRapidUploadInfoByLink(link, &RapidUploadInfo{ 107 ContentLength: finfo.Size, 108 }) 109 } 110 111 // GetRapidUploadInfoByLink 通过下载链接, 获取文件秒传信息 112 func (pcs *BaiduPCS) GetRapidUploadInfoByLink(link string, compareRInfo *RapidUploadInfo) (rinfo *RapidUploadInfo, pcsError pcserror.Error) { 113 errInfo := pcserror.NewPCSErrorInfo(OperationGetRapidUploadInfo) 114 errInfo.ErrType = pcserror.ErrTypeOthers 115 116 var ( 117 header = pcs.getPanUAHeader() 118 isSetRange = compareRInfo != nil && compareRInfo.ContentLength > SliceMD5Size // 是否设置Range 119 ) 120 if isSetRange { 121 header["Range"] = "bytes=0-" + strconv.FormatInt(SliceMD5Size-1, 10) 122 } 123 124 resp, err := pcs.client.Req(http.MethodGet, link, nil, header) 125 if resp != nil { 126 defer resp.Body.Close() 127 } 128 if err != nil { 129 errInfo.SetNetError(err) 130 return nil, errInfo 131 } 132 133 // 检测响应状态码 134 if resp.StatusCode/100 != 2 { 135 errInfo.SetNetError(errors.New(resp.Status)) 136 return nil, errInfo 137 } 138 139 // 检测是否存在MD5 140 md5Str := resp.Header.Get("Content-MD5") 141 if md5Str == "" { // 未找到md5值, 可能是服务器未刷新 142 errInfo.Err = ErrGetRapidUploadInfoMD5NotFound 143 return nil, errInfo 144 } 145 if compareRInfo != nil && compareRInfo.ContentMD5 != "" && compareRInfo.ContentMD5 != md5Str { 146 errInfo.Err = ErrGetRapidUploadInfoMD5NotEqual 147 return nil, errInfo 148 } 149 150 // 获取文件名 151 _, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition")) 152 if err != nil { 153 errInfo.Err = err 154 return nil, errInfo 155 } 156 filename, err := url.QueryUnescape(params["filename"]) 157 if err != nil { 158 errInfo.Err = err 159 return nil, errInfo 160 } 161 if compareRInfo != nil && compareRInfo.Filename != "" && compareRInfo.Filename != filename { 162 errInfo.Err = ErrGetRapidUploadInfoFilenameNotEqual 163 return nil, errInfo 164 } 165 166 var ( 167 contentLength int64 168 ) 169 if isSetRange { 170 // 检测Content-Range 171 contentRange := resp.Header.Get("Content-Range") 172 if contentRange == "" { 173 errInfo.Err = ErrContentRangeNotFound 174 return nil, errInfo 175 } 176 contentLength = downloader.ParseContentRange(contentRange) 177 } else { 178 contentLength = resp.ContentLength 179 } 180 181 // 检测Content-Length 182 switch contentLength { 183 case -1: 184 errInfo.Err = ErrGetRapidUploadInfoLengthNotFound 185 return nil, errInfo 186 case 0: 187 return &RapidUploadInfo{ 188 Filename: filename, 189 ContentLength: contentLength, 190 ContentMD5: EmptyContentMD5, 191 SliceMD5: EmptyContentMD5, 192 ContentCrc32: "0", 193 }, nil 194 default: 195 if compareRInfo != nil && compareRInfo.ContentLength > 0 && compareRInfo.ContentLength != contentLength { 196 errInfo.Err = ErrGetRapidUploadInfoLengthNotEqual 197 return nil, errInfo 198 } 199 } 200 201 // 检测是否存在crc32 值, 一般都会存在的 202 crc32Str := resp.Header.Get("x-bs-meta-crc32") 203 if crc32Str == "" || crc32Str == "0" { 204 errInfo.Err = ErrGetRapidUploadInfoCrc32NotFound 205 return nil, errInfo 206 } 207 if compareRInfo != nil && compareRInfo.ContentCrc32 != "" && compareRInfo.ContentCrc32 != crc32Str { 208 errInfo.Err = ErrGetRapidUploadInfoCrc32NotEqual 209 return nil, errInfo 210 } 211 212 // 获取slice-md5 213 // 忽略比较slice-md5 214 if contentLength <= SliceMD5Size { 215 return &RapidUploadInfo{ 216 Filename: filename, 217 ContentLength: contentLength, 218 ContentMD5: md5Str, 219 SliceMD5: md5Str, 220 ContentCrc32: crc32Str, 221 }, nil 222 } 223 224 buf := cachepool.RawMallocByteSlice(int(SliceMD5Size)) 225 _, err = io.ReadFull(resp.Body, buf) 226 if err != nil { 227 errInfo.SetNetError(err) 228 return nil, errInfo 229 } 230 231 // 计算slice-md5 232 m := md5.New() 233 _, err = m.Write(buf) 234 if err != nil { 235 panic(err) 236 } 237 238 sliceMD5Str := hex.EncodeToString(m.Sum(nil)) 239 240 // 检测slice-md5, 不必要的 241 if compareRInfo != nil && compareRInfo.SliceMD5 != "" && compareRInfo.SliceMD5 != sliceMD5Str { 242 errInfo.Err = ErrGetRapidUploadInfoSliceMD5NotEqual 243 return nil, errInfo 244 } 245 246 return &RapidUploadInfo{ 247 Filename: filename, 248 ContentLength: contentLength, 249 ContentMD5: md5Str, 250 SliceMD5: sliceMD5Str, 251 ContentCrc32: crc32Str, 252 }, nil 253 } 254 255 // FixMD5ByFileInfo 尝试修复文件的md5, 通过文件信息对象 256 func (pcs *BaiduPCS) FixMD5ByFileInfo(finfo *FileDirectory) (pcsError pcserror.Error) { 257 errInfo := pcserror.NewPCSErrorInfo(OperationFixMD5) 258 errInfo.ErrType = pcserror.ErrTypeOthers 259 if finfo == nil { 260 errInfo.Err = ErrFixMD5FileInfoNil 261 return errInfo 262 } 263 264 if finfo.Size > MaxRapidUploadSize { // 文件大于20GB 265 errInfo.Err = ErrFileTooLarge 266 return errInfo 267 } 268 269 // 忽略目录 270 if finfo.Isdir { 271 errInfo.Err = ErrFixMD5Isdir 272 return errInfo 273 } 274 275 if len(finfo.BlockList) == 1 && finfo.BlockList[0] == finfo.MD5 { 276 // 不需要修复 277 return nil 278 } 279 280 link, pcsError := pcs.getLocateDownloadLink(finfo.Path) 281 if pcsError != nil { 282 return pcsError 283 } 284 285 var ( 286 cmpInfo = &RapidUploadInfo{ 287 Filename: finfo.Filename, 288 ContentLength: finfo.Size, 289 } 290 ) 291 rinfo, pcsError := pcs.GetRapidUploadInfoByLink(link, cmpInfo) 292 if pcsError != nil { 293 switch pcsError.GetError() { 294 case ErrGetRapidUploadInfoMD5NotFound, ErrGetRapidUploadInfoCrc32NotFound: 295 errInfo.Err = ErrFixMD5Failed 296 default: 297 errInfo.Err = pcsError 298 } 299 return errInfo 300 } 301 302 // 开始修复 303 return pcs.RapidUploadNoCheckDir(finfo.Path, rinfo.ContentMD5, rinfo.SliceMD5, rinfo.ContentCrc32, rinfo.ContentLength) 304 } 305 306 // FixMD5 尝试修复文件的md5 307 func (pcs *BaiduPCS) FixMD5(pcspath string) (pcsError pcserror.Error) { 308 finfo, pcsError := pcs.FilesDirectoriesMeta(pcspath) 309 if pcsError != nil { 310 return 311 } 312 313 return pcs.FixMD5ByFileInfo(finfo) 314 } 315 316 func (pcs *BaiduPCS) recurseMatchPathByShellPattern(index int, patternSlice *[]string, ps *[]string, pcspaths *[]string) { 317 if index == len(*patternSlice) { 318 *pcspaths = append(*pcspaths, strings.Join(*ps, PathSeparator)) 319 return 320 } 321 322 if !strings.ContainsAny((*patternSlice)[index], ShellPatternCharacters) { 323 (*ps)[index] = (*patternSlice)[index] 324 pcs.recurseMatchPathByShellPattern(index+1, patternSlice, ps, pcspaths) 325 return 326 } 327 328 fds, pcsError := pcs.FilesDirectoriesList(strings.Join((*ps)[:index], PathSeparator), DefaultOrderOptions) 329 if pcsError != nil { 330 panic(pcsError) // 抛出异常 331 } 332 333 for k := range fds { 334 if matched, _ := path.Match((*patternSlice)[index], fds[k].Filename); matched { 335 (*ps)[index] = fds[k].Filename 336 pcs.recurseMatchPathByShellPattern(index+1, patternSlice, ps, pcspaths) 337 } 338 } 339 return 340 } 341 342 // MatchPathByShellPattern 通配符匹配文件路径, pattern 为绝对路径 343 func (pcs *BaiduPCS) MatchPathByShellPattern(pattern string) (pcspaths []string, pcsError pcserror.Error) { 344 errInfo := pcserror.NewPCSErrorInfo(OperrationMatchPathByShellPattern) 345 errInfo.ErrType = pcserror.ErrTypeOthers 346 347 patternSlice := strings.Split(escaper.Escape(path.Clean(pattern), []rune{'['}), PathSeparator) // 转义中括号 348 if patternSlice[0] != "" { 349 errInfo.Err = ErrMatchPathByShellPatternNotAbsPath 350 return nil, errInfo 351 } 352 353 ps := make([]string, len(patternSlice)) 354 defer func() { // 捕获异常 355 if err := recover(); err != nil { 356 pcspaths = nil 357 pcsError = err.(pcserror.Error) 358 } 359 }() 360 pcs.recurseMatchPathByShellPattern(1, &patternSlice, &ps, &pcspaths) 361 return pcspaths, nil 362 }