583 lines
18 KiB
Go
583 lines
18 KiB
Go
package nextcloud
|
|
|
|
import (
|
|
"context"
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// PublicShareView describes a public link share (file or folder).
|
|
type PublicShareView struct {
|
|
Token string `json:"token"`
|
|
Name string `json:"name"`
|
|
ItemType string `json:"item_type"`
|
|
Path string `json:"path"`
|
|
Permissions int `json:"permissions"`
|
|
OwnerID string `json:"owner_id,omitempty"`
|
|
OwnerDisplayName string `json:"owner_displayname,omitempty"`
|
|
Files []FileInfo `json:"files,omitempty"`
|
|
File *FileInfo `json:"file,omitempty"`
|
|
}
|
|
|
|
func (c *Client) PublicShareURL(token string) string {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" || c.drivePublicURL == "" {
|
|
return ""
|
|
}
|
|
return strings.TrimRight(c.drivePublicURL, "/") + "/s/" + url.PathEscape(token)
|
|
}
|
|
|
|
func (c *Client) WithDrivePublicURL(publicURL string) *Client {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
c.drivePublicURL = strings.TrimRight(strings.TrimSpace(publicURL), "/")
|
|
return c
|
|
}
|
|
|
|
func publicShareDAVPath(token, relPath string) string {
|
|
token = strings.TrimSpace(token)
|
|
relPath = NormalizeClientPath(relPath)
|
|
base := "/public.php/dav/files/" + url.PathEscape(token)
|
|
if relPath == "/" {
|
|
return base + "/"
|
|
}
|
|
trimmed := strings.Trim(relPath, "/")
|
|
parts := strings.Split(trimmed, "/")
|
|
for i, p := range parts {
|
|
parts[i] = url.PathEscape(p)
|
|
}
|
|
return base + "/" + strings.Join(parts, "/")
|
|
}
|
|
|
|
func clientPathFromPublicShareHref(href, token string) string {
|
|
href = strings.TrimSpace(href)
|
|
marker := "/public.php/dav/files/" + url.PathEscape(token)
|
|
if idx := strings.Index(href, marker); idx >= 0 {
|
|
rest := strings.TrimPrefix(href[idx+len(marker):], "/")
|
|
if rest == "" {
|
|
return "/"
|
|
}
|
|
return decodeDAVPath("/" + rest)
|
|
}
|
|
// Fallback: strip through token segment in path.
|
|
parts := strings.Split(strings.Trim(href, "/"), "/")
|
|
for i, p := range parts {
|
|
if decodeDAVSegment(p) == token && i+1 < len(parts) {
|
|
return decodeDAVPath("/" + strings.Join(parts[i+1:], "/"))
|
|
}
|
|
}
|
|
return "/"
|
|
}
|
|
|
|
func (c *Client) GetPublicShare(ctx context.Context, token, relPath, password string) (*PublicShareView, error) {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return nil, ErrInvalidPublicShare
|
|
}
|
|
relPath = NormalizeClientPath(relPath)
|
|
|
|
resp, err := c.publicShareRequest(ctx, "PROPFIND", token, relPath, strings.NewReader(propfindListBody), password, map[string]string{
|
|
"Depth": "1",
|
|
"Content-Type": "application/xml",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
return nil, ErrPublicSharePasswordRequired
|
|
}
|
|
if resp.StatusCode != http.StatusMultiStatus && resp.StatusCode != http.StatusOK {
|
|
return nil, &HTTPStatusError{Operation: "public share propfind", StatusCode: resp.StatusCode}
|
|
}
|
|
|
|
root, children, permissions, err := parsePublicSharePropfind(resp.Body, token, relPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
view := &PublicShareView{
|
|
Token: token,
|
|
Path: relPath,
|
|
Name: root.Name,
|
|
Permissions: permissions,
|
|
}
|
|
if sharePerms, permErr := c.GetPublicSharePermissions(ctx, token, password); permErr == nil {
|
|
view.Permissions = sharePerms
|
|
}
|
|
if root.Type == "directory" {
|
|
view.ItemType = "folder"
|
|
view.Files = children
|
|
} else {
|
|
view.ItemType = "file"
|
|
view.File = &root
|
|
}
|
|
c.enrichPublicShareOwner(ctx, view, token, password)
|
|
return view, nil
|
|
}
|
|
|
|
// PreviewPublicShare returns a thumbnail/preview image for a file in a public link share.
|
|
func (c *Client) PreviewPublicShare(ctx context.Context, token, filePath, password string, width, height int) (io.ReadCloser, string, error) {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return nil, "", ErrInvalidPublicShare
|
|
}
|
|
filePath = NormalizeClientPath(filePath)
|
|
if width <= 0 {
|
|
width = 400
|
|
}
|
|
if height <= 0 {
|
|
height = 300
|
|
}
|
|
if width > 2048 {
|
|
width = 2048
|
|
}
|
|
if height > 2048 {
|
|
height = 2048
|
|
}
|
|
|
|
q := url.Values{}
|
|
q.Set("file", filePath)
|
|
q.Set("x", strconv.Itoa(width))
|
|
q.Set("y", strconv.Itoa(height))
|
|
q.Set("a", "1")
|
|
previewPath := fmt.Sprintf(
|
|
"/index.php/apps/files_sharing/publicpreview/%s?%s",
|
|
url.PathEscape(token),
|
|
q.Encode(),
|
|
)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+previewPath, nil)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
req.Header.Set("X-Requested-With", "XMLHttpRequest")
|
|
if password != "" {
|
|
req.SetBasicAuth("", password)
|
|
}
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
resp.Body.Close()
|
|
return nil, "", ErrPublicSharePasswordRequired
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
resp.Body.Close()
|
|
return nil, "", &HTTPStatusError{Operation: "public share preview", StatusCode: resp.StatusCode}
|
|
}
|
|
contentType := strings.TrimSpace(resp.Header.Get("Content-Type"))
|
|
if contentType == "" {
|
|
contentType = "image/jpeg"
|
|
}
|
|
return resp.Body, contentType, nil
|
|
}
|
|
|
|
func (c *Client) DownloadPublicShare(ctx context.Context, token, filePath, password string) (io.ReadCloser, string, error) {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return nil, "", ErrInvalidPublicShare
|
|
}
|
|
filePath = NormalizeClientPath(filePath)
|
|
|
|
body, contentType, err := c.downloadPublicShareAt(ctx, token, filePath, password)
|
|
if err == nil {
|
|
return body, contentType, nil
|
|
}
|
|
// Single-file shares expose content at the WebDAV root, not /filename.
|
|
var statusErr *HTTPStatusError
|
|
if errors.As(err, &statusErr) && statusErr.StatusCode == http.StatusNotFound && filePath != "/" {
|
|
return c.downloadPublicShareAt(ctx, token, "/", password)
|
|
}
|
|
return nil, "", err
|
|
}
|
|
|
|
func (c *Client) downloadPublicShareAt(ctx context.Context, token, filePath, password string) (io.ReadCloser, string, error) {
|
|
davPath := publicShareDAVPath(token, filePath)
|
|
resp, err := c.publicShareRequestRaw(ctx, http.MethodGet, davPath, nil, password, nil)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
resp.Body.Close()
|
|
return nil, "", ErrPublicSharePasswordRequired
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
resp.Body.Close()
|
|
return nil, "", &HTTPStatusError{Operation: "public share download", StatusCode: resp.StatusCode}
|
|
}
|
|
contentType := strings.TrimSpace(resp.Header.Get("Content-Type"))
|
|
if contentType == "" {
|
|
contentType = "application/octet-stream"
|
|
}
|
|
return resp.Body, contentType, nil
|
|
}
|
|
|
|
func (c *Client) publicShareRequest(ctx context.Context, method, token, relPath string, body io.Reader, password string, headers map[string]string) (*http.Response, error) {
|
|
return c.publicShareRequestRaw(ctx, method, publicShareDAVPath(token, relPath), body, password, headers)
|
|
}
|
|
|
|
func (c *Client) publicShareRequestRaw(ctx context.Context, method, davPath string, body io.Reader, password string, headers map[string]string) (*http.Response, error) {
|
|
if !strings.HasPrefix(davPath, "/") {
|
|
davPath = "/" + davPath
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, method, c.baseURL+davPath, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("X-Requested-With", "XMLHttpRequest")
|
|
if password != "" {
|
|
req.SetBasicAuth("", password)
|
|
}
|
|
for k, v := range headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
return c.httpClient.Do(req)
|
|
}
|
|
|
|
const propfindListBody = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
|
<d:prop>
|
|
<d:getlastmodified/>
|
|
<d:getcontenttype/>
|
|
<d:getcontentlength/>
|
|
<d:resourcetype/>
|
|
<d:getetag/>
|
|
<d:displayname/>
|
|
<oc:size/>
|
|
<oc:permissions/>
|
|
</d:prop>
|
|
</d:propfind>`
|
|
|
|
const propfindOwnerBody = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
|
<d:prop>
|
|
<oc:owner-id/>
|
|
</d:prop>
|
|
</d:propfind>`
|
|
|
|
func (c *Client) enrichPublicShareOwner(ctx context.Context, view *PublicShareView, token, password string) {
|
|
if c == nil || view == nil {
|
|
return
|
|
}
|
|
ownerID, err := c.getPublicShareOwnerID(ctx, token, password)
|
|
if err != nil || strings.TrimSpace(ownerID) == "" {
|
|
return
|
|
}
|
|
view.OwnerID = ownerID
|
|
if name, err := c.UserDisplayName(ctx, ownerID); err == nil && strings.TrimSpace(name) != "" {
|
|
view.OwnerDisplayName = strings.TrimSpace(name)
|
|
}
|
|
}
|
|
|
|
func (c *Client) getPublicShareOwnerID(ctx context.Context, token, password string) (string, error) {
|
|
resp, err := c.publicShareRequest(ctx, "PROPFIND", token, "/", strings.NewReader(propfindOwnerBody), password, map[string]string{
|
|
"Depth": "0",
|
|
"Content-Type": "application/xml",
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
return "", ErrPublicSharePasswordRequired
|
|
}
|
|
if resp.StatusCode != http.StatusMultiStatus && resp.StatusCode != http.StatusOK {
|
|
return "", &HTTPStatusError{Operation: "public share owner", StatusCode: resp.StatusCode}
|
|
}
|
|
var ms multistatus
|
|
if err := xml.NewDecoder(resp.Body).Decode(&ms); err != nil {
|
|
return "", err
|
|
}
|
|
if len(ms.Responses) == 0 {
|
|
return "", nil
|
|
}
|
|
return strings.TrimSpace(ms.Responses[0].Propstat.Prop.OwnerID), nil
|
|
}
|
|
|
|
const propfindPermBody = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
|
<d:prop>
|
|
<oc:permissions/>
|
|
</d:prop>
|
|
</d:propfind>`
|
|
|
|
func (c *Client) GetPublicSharePermissions(ctx context.Context, token, password string) (int, error) {
|
|
return c.GetPublicSharePathPermissions(ctx, token, "/", password)
|
|
}
|
|
|
|
// EffectivePublicSharePermissions returns share permissions for a path.
|
|
// Nextcloud often omits oc:permissions on nested WebDAV nodes; fall back to root share bits.
|
|
func (c *Client) EffectivePublicSharePermissions(ctx context.Context, token, relPath, password string) (int, error) {
|
|
root, err := c.GetPublicSharePermissions(ctx, token, password)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
relPath = NormalizeClientPath(relPath)
|
|
if relPath == "/" {
|
|
return root, nil
|
|
}
|
|
pathPerms, err := c.GetPublicSharePathPermissions(ctx, token, relPath, password)
|
|
if err != nil || pathPerms == 0 {
|
|
return root, nil
|
|
}
|
|
return root | pathPerms, nil
|
|
}
|
|
|
|
const propfindPublicRevisionBody = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
|
<d:prop>
|
|
<oc:fileid/>
|
|
<d:getetag/>
|
|
</d:prop>
|
|
</d:propfind>`
|
|
|
|
func (c *Client) PublicShareFileRevision(ctx context.Context, token, filePath, password string) (FileRevision, error) {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return FileRevision{}, ErrInvalidPublicShare
|
|
}
|
|
filePath = NormalizeClientPath(filePath)
|
|
|
|
rev, err := c.publicShareFileRevisionAt(ctx, token, filePath, password)
|
|
if err == nil {
|
|
return rev, nil
|
|
}
|
|
var statusErr *HTTPStatusError
|
|
if errors.As(err, &statusErr) && statusErr.StatusCode == http.StatusNotFound && filePath != "/" {
|
|
return c.publicShareFileRevisionAt(ctx, token, "/", password)
|
|
}
|
|
return FileRevision{}, err
|
|
}
|
|
|
|
func (c *Client) publicShareFileRevisionAt(ctx context.Context, token, filePath, password string) (FileRevision, error) {
|
|
resp, err := c.publicShareRequest(ctx, "PROPFIND", token, filePath, strings.NewReader(propfindPublicRevisionBody), password, map[string]string{
|
|
"Depth": "0",
|
|
"Content-Type": "application/xml",
|
|
})
|
|
if err != nil {
|
|
return FileRevision{}, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
return FileRevision{}, ErrPublicSharePasswordRequired
|
|
}
|
|
if resp.StatusCode != http.StatusMultiStatus && resp.StatusCode != http.StatusOK {
|
|
return FileRevision{}, &HTTPStatusError{Operation: "public share file revision", StatusCode: resp.StatusCode}
|
|
}
|
|
|
|
var ms multistatus
|
|
if err := xml.NewDecoder(resp.Body).Decode(&ms); err != nil {
|
|
return FileRevision{}, err
|
|
}
|
|
if len(ms.Responses) == 0 {
|
|
return FileRevision{}, fmt.Errorf("public share file revision: empty response")
|
|
}
|
|
raw := strings.TrimSpace(ms.Responses[0].Propstat.Prop.FileID)
|
|
if raw == "" {
|
|
return FileRevision{}, fmt.Errorf("public share file revision: missing fileid")
|
|
}
|
|
id, err := strconv.ParseInt(raw, 10, 64)
|
|
if err != nil {
|
|
return FileRevision{}, fmt.Errorf("public share file revision: invalid fileid %q", raw)
|
|
}
|
|
etag := strings.Trim(strings.TrimSpace(ms.Responses[0].Propstat.Prop.ETag), "\"")
|
|
return FileRevision{FileID: id, ETag: etag}, nil
|
|
}
|
|
|
|
func (c *Client) GetPublicSharePathPermissions(ctx context.Context, token, relPath, password string) (int, error) {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return 0, ErrInvalidPublicShare
|
|
}
|
|
relPath = NormalizeClientPath(relPath)
|
|
|
|
resp, err := c.publicShareRequest(ctx, "PROPFIND", token, relPath, strings.NewReader(propfindPermBody), password, map[string]string{
|
|
"Depth": "0",
|
|
"Content-Type": "application/xml",
|
|
})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
return 0, ErrPublicSharePasswordRequired
|
|
}
|
|
if resp.StatusCode != http.StatusMultiStatus && resp.StatusCode != http.StatusOK {
|
|
return 0, &HTTPStatusError{Operation: "public share permissions", StatusCode: resp.StatusCode}
|
|
}
|
|
var ms multistatus
|
|
if err := xml.NewDecoder(resp.Body).Decode(&ms); err != nil {
|
|
return 0, err
|
|
}
|
|
if len(ms.Responses) == 0 {
|
|
return 0, fmt.Errorf("public share: empty permissions response")
|
|
}
|
|
return ParseOCPermissionLetters(ms.Responses[0].Propstat.Prop.Permissions), nil
|
|
}
|
|
|
|
func parsePublicSharePropfind(body io.Reader, token, listDir string) (FileInfo, []FileInfo, int, error) {
|
|
var ms multistatus
|
|
if err := xml.NewDecoder(body).Decode(&ms); err != nil {
|
|
return FileInfo{}, nil, 0, err
|
|
}
|
|
if len(ms.Responses) == 0 {
|
|
return FileInfo{}, nil, 0, fmt.Errorf("public share: empty response")
|
|
}
|
|
|
|
root := fileInfoFromPublicDAV(ms.Responses[0], token, listDir)
|
|
permissions := ParseOCPermissionLetters(ms.Responses[0].Propstat.Prop.Permissions)
|
|
children := make([]FileInfo, 0, len(ms.Responses)-1)
|
|
for i := 1; i < len(ms.Responses); i++ {
|
|
child := fileInfoFromPublicDAV(ms.Responses[i], token, listDir)
|
|
if child.Name == "" {
|
|
continue
|
|
}
|
|
children = append(children, child)
|
|
}
|
|
return root, children, permissions, nil
|
|
}
|
|
|
|
func fileInfoFromPublicDAV(r response, token, listDir string) FileInfo {
|
|
name := fileNameFromDAVProp(r.Propstat.Prop.DisplayName, r.Href)
|
|
clientPath := clientPathFromPublicShareHref(r.Href, token)
|
|
if name == "" {
|
|
name = pathBaseName(strings.Trim(clientPath, "/"))
|
|
}
|
|
if clientPath == "/" && name == "" {
|
|
name = "Partage"
|
|
}
|
|
|
|
fileType := "file"
|
|
if r.Propstat.Prop.ResourceType.Collection != nil {
|
|
fileType = "directory"
|
|
}
|
|
|
|
size := r.Propstat.Prop.ContentLength
|
|
if r.Propstat.Prop.Size > 0 {
|
|
size = r.Propstat.Prop.Size
|
|
}
|
|
|
|
displayPath := clientPath
|
|
if displayPath == "/" && fileType == "file" {
|
|
// Single-file shares: WebDAV root is the file itself.
|
|
displayPath = "/"
|
|
} else if displayPath == "/" {
|
|
displayPath = "/" + name
|
|
}
|
|
|
|
return FileInfo{
|
|
Path: displayPath,
|
|
Name: name,
|
|
Type: fileType,
|
|
Size: size,
|
|
MimeType: r.Propstat.Prop.ContentType,
|
|
LastModified: r.Propstat.Prop.LastModified,
|
|
ETag: strings.Trim(r.Propstat.Prop.ETag, "\""),
|
|
}
|
|
}
|
|
|
|
var (
|
|
ErrInvalidPublicShare = errors.New("invalid public share token")
|
|
ErrPublicSharePasswordRequired = errors.New("public share password required")
|
|
ErrPublicShareReadOnly = errors.New("public share read only")
|
|
)
|
|
|
|
func (c *Client) UploadPublicShare(ctx context.Context, token, filePath, password string, content io.Reader, contentType string) error {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return ErrInvalidPublicShare
|
|
}
|
|
filePath = NormalizeClientPath(filePath)
|
|
headers := map[string]string{}
|
|
if contentType != "" {
|
|
headers["Content-Type"] = contentType
|
|
}
|
|
resp, err := c.publicShareRequest(ctx, http.MethodPut, token, filePath, content, password, headers)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
return ErrPublicSharePasswordRequired
|
|
}
|
|
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
|
|
return &HTTPStatusError{Operation: "public share upload", StatusCode: resp.StatusCode}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) CreatePublicShareFolder(ctx context.Context, token, folderPath, password string) error {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return ErrInvalidPublicShare
|
|
}
|
|
folderPath = NormalizeClientPath(folderPath)
|
|
resp, err := c.publicShareRequest(ctx, "MKCOL", token, folderPath, nil, password, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
return ErrPublicSharePasswordRequired
|
|
}
|
|
if resp.StatusCode != http.StatusCreated {
|
|
return &HTTPStatusError{Operation: "public share mkcol", StatusCode: resp.StatusCode}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) DeletePublicShare(ctx context.Context, token, filePath, password string) error {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return ErrInvalidPublicShare
|
|
}
|
|
filePath = NormalizeClientPath(filePath)
|
|
resp, err := c.publicShareRequest(ctx, http.MethodDelete, token, filePath, nil, password, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
return ErrPublicSharePasswordRequired
|
|
}
|
|
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
|
|
return &HTTPStatusError{Operation: "public share delete", StatusCode: resp.StatusCode}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) MovePublicShare(ctx context.Context, token, srcPath, destPath, password string) error {
|
|
token = strings.TrimSpace(token)
|
|
if token == "" {
|
|
return ErrInvalidPublicShare
|
|
}
|
|
srcPath = NormalizeClientPath(srcPath)
|
|
destPath = NormalizeClientPath(destPath)
|
|
srcDAV := publicShareDAVPath(token, srcPath)
|
|
destDAV := publicShareDAVPath(token, destPath)
|
|
resp, err := c.publicShareRequestRaw(ctx, "MOVE", srcDAV, nil, password, map[string]string{
|
|
"Destination": c.webDAVDestination(destDAV),
|
|
"Overwrite": "F",
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
|
return ErrPublicSharePasswordRequired
|
|
}
|
|
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusNoContent {
|
|
return &HTTPStatusError{Operation: "public share move", StatusCode: resp.StatusCode}
|
|
}
|
|
return nil
|
|
}
|