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  }