| package onedrive_sharelink |
|
|
| import ( |
| "crypto/tls" |
| "encoding/json" |
| "fmt" |
| "io" |
| "net/http" |
| "net/url" |
| "regexp" |
| "strings" |
| "time" |
|
|
| "github.com/alist-org/alist/v3/drivers/base" |
| "github.com/alist-org/alist/v3/internal/conf" |
| log "github.com/sirupsen/logrus" |
| "golang.org/x/net/html" |
| ) |
|
|
| |
| func NewNoRedirectCLient() *http.Client { |
| return &http.Client{ |
| Timeout: time.Hour * 48, |
| Transport: &http.Transport{ |
| Proxy: http.ProxyFromEnvironment, |
| TLSClientConfig: &tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify}, |
| }, |
| |
| CheckRedirect: func(req *http.Request, via []*http.Request) error { |
| return http.ErrUseLastResponse |
| }, |
| } |
| } |
|
|
| |
| func getCookiesWithPassword(link, password string) (string, error) { |
| |
| resp, err := http.Get(link) |
| if err != nil { |
| return "", err |
| } |
| defer resp.Body.Close() |
|
|
| |
| doc, err := html.Parse(resp.Body) |
| if err != nil { |
| return "", err |
| } |
|
|
| |
| var viewstate, eventvalidation, postAction string |
|
|
| |
| var findInputFields func(*html.Node) |
| findInputFields = func(n *html.Node) { |
| if n.Type == html.ElementNode && n.Data == "input" { |
| for _, attr := range n.Attr { |
| if attr.Key == "id" { |
| switch attr.Val { |
| case "__VIEWSTATE": |
| viewstate = getAttrValue(n, "value") |
| case "__EVENTVALIDATION": |
| eventvalidation = getAttrValue(n, "value") |
| } |
| } |
| } |
| } |
| if n.Type == html.ElementNode && n.Data == "form" { |
| for _, attr := range n.Attr { |
| if attr.Key == "id" && attr.Val == "inputForm" { |
| postAction = getAttrValue(n, "action") |
| } |
| } |
| } |
| for c := n.FirstChild; c != nil; c = c.NextSibling { |
| findInputFields(c) |
| } |
| } |
| findInputFields(doc) |
|
|
| |
| linkParts, err := url.Parse(link) |
| if err != nil { |
| return "", err |
| } |
|
|
| newURL := fmt.Sprintf("%s://%s%s", linkParts.Scheme, linkParts.Host, postAction) |
|
|
| |
| data := url.Values{ |
| "txtPassword": []string{password}, |
| "__EVENTVALIDATION": []string{eventvalidation}, |
| "__VIEWSTATE": []string{viewstate}, |
| "__VIEWSTATEENCRYPTED": []string{""}, |
| } |
|
|
| client := &http.Client{ |
| CheckRedirect: func(req *http.Request, via []*http.Request) error { |
| return http.ErrUseLastResponse |
| }, |
| } |
| |
| resp, err = client.PostForm(newURL, data) |
| if err != nil { |
| return "", err |
| } |
|
|
| |
| cookie := resp.Cookies() |
| var fedAuthCookie string |
| for _, c := range cookie { |
| if c.Name == "FedAuth" { |
| fedAuthCookie = c.Value |
| break |
| } |
| } |
| if fedAuthCookie == "" { |
| return "", fmt.Errorf("wrong password") |
| } |
| return fmt.Sprintf("FedAuth=%s;", fedAuthCookie), nil |
| } |
|
|
| |
| func getAttrValue(n *html.Node, key string) string { |
| for _, attr := range n.Attr { |
| if attr.Key == key { |
| return attr.Val |
| } |
| } |
| return "" |
| } |
|
|
| |
| func (d *OnedriveSharelink) getHeaders() (http.Header, error) { |
| header := http.Header{} |
| header.Set("User-Agent", base.UserAgent) |
| header.Set("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") |
|
|
| |
| d.HeaderTime = time.Now().Unix() |
|
|
| if d.ShareLinkPassword == "" { |
| |
| clientNoDirect := NewNoRedirectCLient() |
| req, err := http.NewRequest("GET", d.ShareLinkURL, nil) |
| if err != nil { |
| return nil, err |
| } |
| |
| req.Header = header |
| answerNoRedirect, err := clientNoDirect.Do(req) |
| if err != nil { |
| return nil, err |
| } |
| redirectUrl := answerNoRedirect.Header.Get("Location") |
| log.Debugln("redirectUrl:", redirectUrl) |
| if redirectUrl == "" { |
| return nil, fmt.Errorf("password protected link. Please provide password") |
| } |
| header.Set("Cookie", answerNoRedirect.Header.Get("Set-Cookie")) |
| header.Set("Referer", redirectUrl) |
|
|
| |
| u, err := url.Parse(redirectUrl) |
| if err != nil { |
| return nil, err |
| } |
| header.Set("authority", u.Host) |
| return header, nil |
| } else { |
| cookie, err := getCookiesWithPassword(d.ShareLinkURL, d.ShareLinkPassword) |
| if err != nil { |
| return nil, err |
| } |
| header.Set("Cookie", cookie) |
| header.Set("Referer", d.ShareLinkURL) |
| header.Set("authority", strings.Split(strings.Split(d.ShareLinkURL, "//")[1], "/")[0]) |
| return header, nil |
| } |
| } |
|
|
| |
| func (d *OnedriveSharelink) getFiles(path string) ([]Item, error) { |
| clientNoDirect := NewNoRedirectCLient() |
| req, err := http.NewRequest("GET", d.ShareLinkURL, nil) |
| if err != nil { |
| return nil, err |
| } |
| header := req.Header |
| redirectUrl := "" |
| if d.ShareLinkPassword == "" { |
| header.Set("User-Agent", base.UserAgent) |
| header.Set("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") |
| req.Header = header |
| answerNoRedirect, err := clientNoDirect.Do(req) |
| if err != nil { |
| return nil, err |
| } |
| redirectUrl = answerNoRedirect.Header.Get("Location") |
| } else { |
| header = d.Headers |
| req.Header = header |
| answerNoRedirect, err := clientNoDirect.Do(req) |
| if err != nil { |
| return nil, err |
| } |
| redirectUrl = answerNoRedirect.Header.Get("Location") |
| } |
| redirectSplitURL := strings.Split(redirectUrl, "/") |
| req.Header = d.Headers |
| downloadLinkPrefix := "" |
| rootFolderPre := "" |
|
|
| |
| if d.IsSharepoint { |
| |
| req.URL, err = url.Parse(redirectUrl) |
| if err != nil { |
| return nil, err |
| } |
| |
| answer, err := clientNoDirect.Do(req) |
| if err != nil { |
| d.Headers, err = d.getHeaders() |
| if err != nil { |
| return nil, err |
| } |
| return d.getFiles(path) |
| } |
| defer answer.Body.Close() |
| re := regexp.MustCompile(`templateUrl":"(.*?)"`) |
| body, err := io.ReadAll(answer.Body) |
| if err != nil { |
| return nil, err |
| } |
| template := re.FindString(string(body)) |
| template = template[strings.Index(template, "templateUrl\":\"")+len("templateUrl\":\""):] |
| template = template[:strings.Index(template, "?id=")] |
| template = template[:strings.LastIndex(template, "/")] |
| downloadLinkPrefix = template + "/download.aspx?UniqueId=" |
| params, err := url.ParseQuery(redirectUrl[strings.Index(redirectUrl, "?")+1:]) |
| if err != nil { |
| return nil, err |
| } |
| rootFolderPre = params.Get("id") |
| } else { |
| redirectUrlCut := redirectUrl[:strings.LastIndex(redirectUrl, "/")] |
| downloadLinkPrefix = redirectUrlCut + "/download.aspx?UniqueId=" |
| params, err := url.ParseQuery(redirectUrl[strings.Index(redirectUrl, "?")+1:]) |
| if err != nil { |
| return nil, err |
| } |
| rootFolderPre = params.Get("id") |
| } |
| d.downloadLinkPrefix = downloadLinkPrefix |
| rootFolder, err := url.QueryUnescape(rootFolderPre) |
| if err != nil { |
| return nil, err |
| } |
| log.Debugln("rootFolder:", rootFolder) |
| |
| relativePath := strings.Split(rootFolder, "Documents")[0] + "Documents" |
|
|
| |
| relativeUrl := url.QueryEscape(relativePath) |
| |
| relativeUrl = strings.Replace(relativeUrl, "_", "%5F", -1) |
| relativeUrl = strings.Replace(relativeUrl, "-", "%2D", -1) |
|
|
| |
| if path != "/" { |
| rootFolder = rootFolder + path |
| } |
|
|
| |
| rootFolderUrl := url.QueryEscape(rootFolder) |
| |
| rootFolderUrl = strings.Replace(rootFolderUrl, "_", "%5F", -1) |
| rootFolderUrl = strings.Replace(rootFolderUrl, "-", "%2D", -1) |
|
|
| log.Debugln("relativePath:", relativePath, "relativeUrl:", relativeUrl, "rootFolder:", rootFolder, "rootFolderUrl:", rootFolderUrl) |
|
|
| |
| graphqlVar := fmt.Sprintf(`{"query":"query (\n $listServerRelativeUrl: String!,$renderListDataAsStreamParameters: RenderListDataAsStreamParameters!,$renderListDataAsStreamQueryString: String!\n )\n {\n \n legacy {\n \n renderListDataAsStream(\n listServerRelativeUrl: $listServerRelativeUrl,\n parameters: $renderListDataAsStreamParameters,\n queryString: $renderListDataAsStreamQueryString\n )\n }\n \n \n perf {\n executionTime\n overheadTime\n parsingTime\n queryCount\n validationTime\n resolvers {\n name\n queryCount\n resolveTime\n waitTime\n }\n }\n }","variables":{"listServerRelativeUrl":"%s","renderListDataAsStreamParameters":{"renderOptions":5707527,"allowMultipleValueFilterForTaxonomyFields":true,"addRequiredFields":true,"folderServerRelativeUrl":"%s"},"renderListDataAsStreamQueryString":"@a1=\'%s\'&RootFolder=%s&TryNewExperienceSingle=TRUE"}}`, relativePath, rootFolder, relativeUrl, rootFolderUrl) |
| tempHeader := make(http.Header) |
| for k, v := range d.Headers { |
| tempHeader[k] = v |
| } |
| tempHeader["Content-Type"] = []string{"application/json;odata=verbose"} |
|
|
| client := &http.Client{} |
| postUrl := strings.Join(redirectSplitURL[:len(redirectSplitURL)-3], "/") + "/_api/v2.1/graphql" |
| req, err = http.NewRequest("POST", postUrl, strings.NewReader(graphqlVar)) |
| if err != nil { |
| return nil, err |
| } |
| req.Header = tempHeader |
|
|
| resp, err := client.Do(req) |
| if err != nil { |
| d.Headers, err = d.getHeaders() |
| if err != nil { |
| return nil, err |
| } |
| return d.getFiles(path) |
| } |
| defer resp.Body.Close() |
| var graphqlReq GraphQLRequest |
| json.NewDecoder(resp.Body).Decode(&graphqlReq) |
| log.Debugln("graphqlReq:", graphqlReq) |
| filesData := graphqlReq.Data.Legacy.RenderListDataAsStream.ListData.Row |
| if graphqlReq.Data.Legacy.RenderListDataAsStream.ListData.NextHref != "" { |
| nextHref := graphqlReq.Data.Legacy.RenderListDataAsStream.ListData.NextHref + "&@a1=REPLACEME&TryNewExperienceSingle=TRUE" |
| nextHref = strings.Replace(nextHref, "REPLACEME", "%27"+relativeUrl+"%27", -1) |
| log.Debugln("nextHref:", nextHref) |
| filesData = append(filesData, graphqlReq.Data.Legacy.RenderListDataAsStream.ListData.Row...) |
|
|
| listViewXml := graphqlReq.Data.Legacy.RenderListDataAsStream.ViewMetadata.ListViewXml |
| log.Debugln("listViewXml:", listViewXml) |
| renderListDataAsStreamVar := `{"parameters":{"__metadata":{"type":"SP.RenderListDataParameters"},"RenderOptions":1216519,"ViewXml":"REPLACEME","AllowMultipleValueFilterForTaxonomyFields":true,"AddRequiredFields":true}}` |
| listViewXml = strings.Replace(listViewXml, `"`, `\"`, -1) |
| renderListDataAsStreamVar = strings.Replace(renderListDataAsStreamVar, "REPLACEME", listViewXml, -1) |
|
|
| graphqlReqNEW := GraphQLNEWRequest{} |
| postUrl = strings.Join(redirectSplitURL[:len(redirectSplitURL)-3], "/") + "/_api/web/GetListUsingPath(DecodedUrl=@a1)/RenderListDataAsStream" + nextHref |
| req, _ = http.NewRequest("POST", postUrl, strings.NewReader(renderListDataAsStreamVar)) |
| req.Header = tempHeader |
|
|
| resp, err := client.Do(req) |
| if err != nil { |
| d.Headers, err = d.getHeaders() |
| if err != nil { |
| return nil, err |
| } |
| return d.getFiles(path) |
| } |
| defer resp.Body.Close() |
| json.NewDecoder(resp.Body).Decode(&graphqlReqNEW) |
| for graphqlReqNEW.ListData.NextHref != "" { |
| graphqlReqNEW = GraphQLNEWRequest{} |
| postUrl = strings.Join(redirectSplitURL[:len(redirectSplitURL)-3], "/") + "/_api/web/GetListUsingPath(DecodedUrl=@a1)/RenderListDataAsStream" + nextHref |
| req, _ = http.NewRequest("POST", postUrl, strings.NewReader(renderListDataAsStreamVar)) |
| req.Header = tempHeader |
| resp, err := client.Do(req) |
| if err != nil { |
| d.Headers, err = d.getHeaders() |
| if err != nil { |
| return nil, err |
| } |
| return d.getFiles(path) |
| } |
| defer resp.Body.Close() |
| json.NewDecoder(resp.Body).Decode(&graphqlReqNEW) |
| nextHref = graphqlReqNEW.ListData.NextHref + "&@a1=REPLACEME&TryNewExperienceSingle=TRUE" |
| nextHref = strings.Replace(nextHref, "REPLACEME", "%27"+relativeUrl+"%27", -1) |
| filesData = append(filesData, graphqlReqNEW.ListData.Row...) |
| } |
| filesData = append(filesData, graphqlReqNEW.ListData.Row...) |
| } else { |
| filesData = append(filesData, graphqlReq.Data.Legacy.RenderListDataAsStream.ListData.Row...) |
| } |
| return filesData, nil |
| } |
|
|