import { config } from "dotenv" ; config ( ) ; import Joi from "joi" ; import UploadFilesCommand , { UploadFilesCommandOptions , SourceTargetFileNames } from "./command/uploadFilesCommand" ; import UploadFolderCommand , { UploadFolderCommandOptions } from "./command/uploadFolderCommand" ; import GetFilesRecursivelyCommand , { GetFilesRecursivelyCommandOptions } from "./command/getFilesRecursivelyCommand" ; import DownloadFolderCommand , { DownloadFolderCommandOptions } from "./command/downloadFolderCommand" ; import { CommandStatus , CommandResultMetaData } from "./command/command" ; import parser from "fast-xml-parser" ; import { Headers , RequestInit , Response } from "node-fetch" ; import path , { basename } from "path" ; import Environment from "./environment" ; import EnvironmentVcapServices from "./environmentVcapServices" ; import ClientError , { CommandAlreadyExecutedError , QueryLimitError , QueryOffsetError , InsufficientPrivilegesError , InvalidServiceResponseFormatError , OperationFailedError , UserGroupAlreadyExistsError , UserGroupDeletionFailedError , UserResendWelcomeEmailError , UserGroupDoesNotExistError , UserNotFoundError , UserAlreadyExistsError , UserCreateError , UserUpdateError , } from "./error" ; import FakeServer from "./fakeServer" ; import File from "./file" ; import FileSystemElement from "./fileSystemElement" ; import FileSystemFolder , { IFileNameFormats } from "./fileSystemFolder" ; import Folder , { FolderGetFilesOptions } from "./folder" ; import { HttpClient , IHttpClientOptions , IProxy , IRequestContext } from "./httpClient" ; import RequestResponseLog from "./requestResponseLog" ; import RequestResponseLogEntry from "./requestResponseLogEntry" ; import Server , { IServerOptions } from "./server" ; import Share , { ICreateShare , SharePermission , ShareItemType } from "./share" ; import Tag from "./tag" ; import UserGroup from "./userGroup" ; import User , { IUserOptions , IUserOptionsQuota , IUserQuotaUserFriendly , UserProperty } from "./user" ; import Logger from "./logger" ; import { isNumber } from "util" ; const log = new Logger ( ) ; export { FolderGetFilesOptions , CommandAlreadyExecutedError , InvalidServiceResponseFormatError , InsufficientPrivilegesError , OperationFailedError , QueryLimitError , QueryOffsetError , UserNotFoundError , UserAlreadyExistsError , UserCreateError , UserResendWelcomeEmailError , UserUpdateError , UserGroupAlreadyExistsError , UserGroupDeletionFailedError , UserGroupDoesNotExistError , } ; export { Client , ClientError , Environment , Folder , File , FileSystemElement , ICreateShare , IServerOptions , Tag , FakeServer , Server , Share , SharePermission , RequestResponseLog , RequestResponseLogEntry , User , UserGroup , UserProperty , IUserOptionsQuota , IUserQuotaUserFriendly , ShareItemType , } ; export { CommandResultMetaData , DownloadFolderCommand , DownloadFolderCommandOptions , GetFilesRecursivelyCommand , GetFilesRecursivelyCommandOptions , UploadFilesCommand , UploadFilesCommandOptions , UploadFolderCommand , UploadFolderCommandOptions , SourceTargetFileNames , FileSystemFolder , IFileNameFormats , CommandStatus , } ; interface IStat { type : string ; filename : string ; basename : string ; lastmod : string ; size ? : number ; mime ? : string ; fileid ? : number ; } export interface IUpsertUserOptions { id : string ; enabled ? : boolean ; subadminGroups ? : string [ ] ; memberGroups ? : string [ ] ; quota ? : string ; email ? : string ; displayName ? : string ; password ? : string ; phone ? : string ; address ? : string ; website ? : string ; twitter ? : string ; language ? : string ; locale ? : string ; superAdmin ? : boolean ; resendWelcomeEmail ? : boolean ; } export interface IUserPropertyChange { property : string ; previousValue : string ; newValue : string ; error ? : string ; } export interface IUpsertUserReport { id : string ; message : string ; changes : IUserPropertyChange [ ] ; } export interface ISysInfoNextcloudClient { version : string ; } export interface ISysInfoNextcloud { system : any ; storage : any ; shares : any ; } export interface ISysBasicData { serverTimeString : string ; uptimeString : string ; timeServersString : string ; } export interface ISystemInfo { nextcloud : ISysInfoNextcloud ; server : any ; activeUsers : any ; nextcloudClient : ISysInfoNextcloudClient ; } export interface IQuota { used : number ; available : number | string ; } /**
 * The nextcloud client is the root object to access the remote api of the nextcloud server.<br>
 */ export default class Client { public static webDavUrlPath : string = "/remote.php/webdav" ; private nextcloudOrigin : string ; private nextcloudAuthHeader : string ; private nextcloudRequestToken : string ; private webDAVUrl : string ; private proxy ? : IProxy ; private fakeServer ? : FakeServer ; private logRequestResponse : boolean = false ; private httpClient ? : HttpClient ; private userId : string ; param server constructor public constructor ( server ? ) { log . debug ( "constructor" ) ; this . nextcloudOrigin = "" ; this . nextcloudAuthHeader = "" ; this . nextcloudRequestToken = "" ; this . webDAVUrl = "" ; this . userId = "" ; if ( ! server ) { try { const env = new EnvironmentVcapServices ( "nextcloud" ) ; server = env . getServer ( ) ; } catch ( e ) { const serverOptions = { url : Environment . getNextcloudUrl ( ) , basicAuth : { username : Environment . getUserName ( ) , password : Environment . getPassword ( ) , } , logRequestResponse : Environment . getRecordingActiveIndicator ( ) , } ; server = new Server ( serverOptions ) ; } } if ( server instanceof Server ) { this . proxy = server . proxy ; log . debug ( "constructor: url " , server . url ) ; if ( server . url . indexOf ( Client . webDavUrlPath ) === - 1 ) { if ( server . url . slice ( - 1 ) === "/" ) { server . url = server . url . slice ( 0 , - 1 ) ; } server . url = server . url + Client . webDavUrlPath ; } this . nextcloudOrigin = server . url . substr ( 0 , server . url . indexOf ( Client . webDavUrlPath ) ) ; log . debug ( "constructor: nextcloud url " , this . nextcloudOrigin ) ; this . userId = server . basicAuth . username ; this . nextcloudAuthHeader = "Basic " + Buffer . from ( server . basicAuth . username + ":" + server . basicAuth . password ) . toString ( "base64" ) ; this . nextcloudRequestToken = "" ; if ( server . url . slice ( - 1 ) === "/" ) { this . webDAVUrl = server . url . slice ( 0 , - 1 ) ; } else { this . webDAVUrl = server . url ; } this . logRequestResponse = server . logRequestResponse ; const options = { authorizationHeader : this . nextcloudAuthHeader , logRequestResponse : this . logRequestResponse , origin : this . nextcloudOrigin , proxy : this . proxy , } ; this . httpClient = new HttpClient ( options ) ; } if ( server instanceof FakeServer ) { this . fakeServer = server ; this . webDAVUrl = "https://fake.server" + Client . webDavUrlPath ; } } /**
     * returns the used and free quota of the nextcloud account
     */ public async getQuota ( ) { log . debug ( "getQuota" ) ; const requestInit = { method : "PROPFIND" , } ; const response = await this . getHttpResponse ( this . webDAVUrl + "/" , requestInit , [ 207 ] , { description : "Client get quota" } ) ; const properties = await this . getPropertiesFromWebDAVMultistatusResponse ( response , Client . webDavUrlPath + "/" ) ; let quota = null ; for ( const prop of properties ) { if ( prop [ "quota-available-bytes" ] ) { quota = { available : "unlimited" , used : prop [ "quota-used-bytes" ] , } ; if ( prop [ "quota-available-bytes" ] > 0 ) { quota . available = prop [ "quota-available-bytes" ] ; } } } if ( ! quota ) { log . debug ( "Error, quota not available: " , JSON . stringify ( properties , null , 4 ) ) ; throw new ClientError ( `Error, quota not available` , "ERR_QUOTA_NOT_AVAILABLE" ) ; } log . debug ( "getQuota =" , quota ) ; return quota ; } param tagName returns public async createTag ( tagName ) { log . debug ( "createTag" ) ; let tag ; tag = await this . getTagByName ( tagName ) ; if ( tag ) { return tag ; } const requestInit = { body : `{ "name": "${ tagName }", "userVisible": true, "userAssignable": true, "canAssign": true }` , headers : new Headers ( { "Content-Type" : "application/json" } ) , method : "POST" , } ; const response = await this . getHttpResponse ( this . nextcloudOrigin + "/remote.php/dav/systemtags/" , requestInit , [ 201 ] , { description : "Tag create" } ) ; const tagString = response . headers . get ( "Content-Location" ) ; if ( tagString === "" || tagString === null ) { log . error ( `createTag 'tagName' ${ tagName }` ) ; throw new ClientError ( `Error, tag with name '${ tagName }' could not be created` , "ERR_TAG_CREATE_FAILED" ) ; } log . debug ( `createTag new tagId ${ tagString } tagName ${ tagName }` ) ; const tagId = this . getTagIdFromHref ( tagString ) ; tag = new Tag ( this , tagId , tagName , true , true , true ) ; return tag ; } param tagName returns public async getTagByName ( tagName ) { log . debug ( "getTag" ) ; const tags = await this . getTags ( ) ; for ( const tag of tags ) { if ( tag . name === tagName ) { return tag ; } } return null ; } param tagId returns public async getTagById ( tagId ) { log . debug ( "getTagById" ) ; const tags = await this . getTags ( ) ; for ( const tag of tags ) { if ( tag . id === tagId ) { return tag ; } } return null ; } param tagId public async deleteTag ( tagId ) { log . debug ( "deleteTag tagId: " , tagId ) ; const requestInit = { method : "DELETE" , } ; const response = await this . getHttpResponse ( `${ this . nextcloudOrigin }/remote.php/dav/systemtags/${ tagId }` , requestInit , [ 204 , 404 ] , { description : "Tag delete" } ) ; } throws public async deleteAllTags ( ) { log . debug ( "deleteAllTags" ) ; const tags = await this . getTags ( ) ; for ( const tag of tags ) { await tag . delete ( ) ; } } returns public async getTags ( ) { log . debug ( "getTags PROPFIND " + this . nextcloudOrigin + "/remote.php/dav/systemtags/" ) ; const requestInit = { body : `<?xml version="1.0"?>
            <d:propfind  xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
              <d:prop>
                <oc:id />
                <oc:display-name />
                <oc:user-visible />
                <oc:user-assignable />
                <oc:can-assign />
              </d:prop>
            </d:propfind>` , method : "PROPFIND" , } ; const relUrl = `/remote.php/dav/systemtags/` ; const response = await this . getHttpResponse ( this . nextcloudOrigin + relUrl , requestInit , [ 207 ] , { description : "Tags get" } ) ; const properties = await this . getPropertiesFromWebDAVMultistatusResponse ( response , relUrl + "/*" ) ; const tags = [ ] ; for ( const prop of properties ) { tags . push ( new Tag ( this , this . getTagIdFromHref ( prop . _href ) , prop [ "display-name" ] , prop [ "user-visible" ] , prop [ "user-assignable" ] , prop [ "can-assign" ] ) ) ; } return tags ; } param fileId public async getTagsOfFile ( fileId ) { log . debug ( "getTagsOfFile" ) ; const requestInit = { body : `<?xml version="1.0"?>
            <d:propfind  xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
              <d:prop>
                <oc:id />
                <oc:display-name />
                <oc:user-visible />
                <oc:user-assignable />
                <oc:can-assign />
              </d:prop>
            </d:propfind>` , method : "PROPFIND" , } ; const relUrl = `/remote.php/dav/systemtags-relations/files/${ fileId }` ; const response = await this . getHttpResponse ( `${ this . nextcloudOrigin }${ relUrl }` , requestInit , [ 207 ] , { description : "File get tags" } ) ; const properties = await this . getPropertiesFromWebDAVMultistatusResponse ( response , relUrl + "/*" ) ; const tagMap = new Map ( ) ; for ( const prop of properties ) { tagMap . set ( prop [ "display-name" ] , prop . id ) ; } log . debug ( "tags of file " , tagMap ) ; return tagMap ; } param fileId param tagId public async removeTagOfFile ( fileId , tagId ) { log . debug ( `removeTagOfFile tagId: ${ tagId } fileId:${ fileId }` ) ; const requestInit = { method : "DELETE" , } ; await this . getHttpResponse ( `${ this . nextcloudOrigin }/remote.php/dav/systemtags-relations/files/${ fileId }/${ tagId }` , requestInit , [ 204 , 404 ] , { description : "File remove tag" } ) ; return ; } returns public async getFileId ( fileUrl ) { log . debug ( "getFileId" ) ; const requestInit = { body : `
            <d:propfind  xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
              <d:prop>
                  <oc:fileid />
              </d:prop>
            </d:propfind>` , method : "PROPFIND" , } ; const response = await this . getHttpResponse ( fileUrl , requestInit , [ 207 ] , { description : "File get id" } ) ; const properties = await this . getPropertiesFromWebDAVMultistatusResponse ( response , "" ) ; for ( const prop of properties ) { if ( prop . fileid !== undefined ) { if ( typeof prop . fileid === "number" ) { return prop . fileid as number ; } else { return - 1 ; } } } log . debug ( "getFileId no file id found for " + fileUrl ) ; return - 1 ; } public async getFolderContents ( folderName ) { log . debug ( "getFolderContents" ) ; const requestInit = { body : `<?xml version="1.0"?>
            <d:propfind  xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns" xmlns:ocs="http://open-collaboration-services.org/ns">
              <d:prop>
                <d:getlastmodified />
                <d:getetag />
                <d:getcontenttype />
                <d:resourcetype />
                <oc:fileid />
                <oc:permissions />
                <oc:size />
                <d:getcontentlength />
                <nc:has-preview />
                <nc:mount-type />
                <nc:is-encrypted />
                <ocs:share-permissions />
                <oc:tags />
                <oc:favorite />
                <oc:comments-unread />
                <oc:owner-id />
                <oc:owner-display-name />
                <oc:share-types />
              </d:prop>
            </d:propfind>` , method : "PROPFIND" , } ; const url = `${ this . webDAVUrl }${ folderName }` ; const response = await this . getHttpResponse ( url , requestInit , [ 207 ] , { description : "Folder get contents" } ) ; const folderContents = [ ] ; const schema = Joi . object ( { _href : Joi . string ( ) . required ( ) , getlastmodified : Joi . string ( ) . required ( ) , fileid : Joi . number ( ) . integer ( ) . required ( ) , getcontenttype : Joi . string ( ) , getcontentlength : Joi . number ( ) . integer ( ) , } ) ; const properties = await this . getPropertiesFromWebDAVMultistatusResponse ( response , "" ) ; const { error , value } = schema . validate ( properties ) ; if ( error ) { throw new ClientError ( `Error get folder contents - folder name "${ folderName }" error ${ error . message };` , "INVALID" ) ; } for ( const prop of properties ) { let fileName = decodeURI ( prop . _href . substr ( prop . _href . indexOf ( Client . webDavUrlPath ) + 18 ) ) ; if ( fileName . endsWith ( "/" ) ) { fileName = fileName . slice ( 0 , - 1 ) ; } if ( ( url + "/" ) . endsWith ( decodeURI ( prop . _href ) ) ) { continue ; } const folderContentsEntry = { } ; folderContentsEntry . lastmod = prop . getlastmodified ; folderContentsEntry . fileid = prop . fileid ; folderContentsEntry . basename = fileName . split ( "/" ) . reverse ( ) [ 0 ] ; folderContentsEntry . filename = fileName ; if ( prop . getcontenttype ) { folderContentsEntry . mime = prop . getcontenttype ; folderContentsEntry . size = prop . getcontentlength ; folderContentsEntry . type = "file" ; } else { folderContentsEntry . type = "directory" ; } folderContents . push ( folderContentsEntry ) ; } return folderContents ; } param folderName returns public async createFolder ( folderName ) { folderName = this . sanitizeFolderName ( folderName ) ; log . debug ( "createFolder: folderName=" , folderName ) ; const parts1 = folderName . split ( "/" ) ; for ( const p of parts1 ) { if ( p === "." || p === ".." ) { throw new ClientError ( `Error creating folder, folder name "${ folderName }" invalid` , "ERR_CREATE_FOLDER_INVALID_FOLDER_NAME" ) ; } } let folder ; folder = await this . getFolder ( folderName ) ; if ( folder ) { log . debug ( "createFolder: folder already available " , folder . name ) ; return folder ; } else { try { log . debug ( "createFolder: folder = " , folderName ) ; await this . createFolderInternal ( folderName ) ; } catch ( e ) { const parts = folderName . split ( "/" ) ; parts . shift ( ) ; let folderPath = "" ; log . debug ( "createFolder: parts = " , parts ) ; for ( const part of parts ) { log . debug ( "createFolder: part = " , part ) ; folderPath += "/" + part ; folder = await this . getFolder ( folderPath ) ; if ( folder === null ) { log . debug ( "createFolder: folder not available" ) ; log . debug ( "createFolder: folder = " , folderPath ) ; await this . createFolderInternal ( folderPath ) ; } else { log . debug ( "createFolder: folder already available " , folderPath ) ; } } } } folder = await this . getFolder ( folderName ) ; if ( folder ) { log . debug ( "createFolder: new folder " , folder . name ) ; return folder ; } else { throw new ClientError ( `Error creating folder, folder name "${ folderName }"
            ` , "ERR_CREATE_FOLDER_FAILED" ) ; } } param fileName public async deleteFile ( fileName ) { const url = this . webDAVUrl + fileName ; log . debug ( "deleteFile " , url ) ; const requestInit = { method : "DELETE" , } ; try { await this . getHttpResponse ( url , requestInit , [ 204 ] , { description : "File delete" } ) ; } catch ( err ) { log . debug ( "Error in deleteFile " , err . message , requestInit . method , url ) ; throw err ; } } param folderName public async deleteFolder ( folderName ) { folderName = this . sanitizeFolderName ( folderName ) ; log . debug ( "deleteFolder:" ) ; const folder = await this . getFolder ( folderName ) ; if ( folder ) { await this . deleteFile ( folderName ) ; } } returns { Promise < Folder > } public getRootFolder ( ) { return new Folder ( this , "/" , "" , "" ) ; } param { Tag [ ] } tags async returns { Promise < FileSystemElement [ ] > } public async getFileSystemElementByTags ( tags ) { log . debug ( "getFileSystemElementByTags " , tags . join ( ", " ) ) ; let filterRule = "" ; for ( const tag of tags ) { filterRule += `<oc:systemtag>${ tag . id }</oc:systemtag>` ; } const urlSuffix = `/remote.php/dav/files/` ; const url = `${ this . nextcloudOrigin }${ urlSuffix }${ this . userId }` ; const body = `<?xml version="1.0"?>
        <oc:filter-files  xmlns:d=\"DAV:\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\" xmlns:ocs=\"http://open-collaboration-services.org/ns\">
           <d:prop>
              <d:getcontenttype />
              <oc:fileid />
           </d:prop>
           <oc:filter-rules>
                ${ filterRule }
           </oc:filter-rules>
        </oc:filter-files>` ; const requestInit = { body , method : "REPORT" , } ; let response ; try { response = await this . getHttpResponse ( url , requestInit , [ 207 ] , { description : "Get FileSystemElements by tags" } ) ; } catch ( err ) { log . debug ( "Error in stat " , err . message , requestInit . method , url ) ; throw err ; } const result = [ ] ; let properties = [ ] ; try { properties = await this . getPropertiesFromWebDAVMultistatusResponse ( response , "" ) ; } catch ( e ) { return result ; } for ( const prop of properties ) { let fse = null ; let name = prop . _href ; name = name . replace ( urlSuffix , "" ) ; const a = name . split ( "/" ) ; a . shift ( ) ; name = "/" + a . join ( "/" ) ; if ( prop . getcontenttype ) { fse = await this . getFile ( name ) ; } else { fse = await this . getFolder ( name ) ; } result . push ( fse ! ) ; } return result ; } param { string } folderName returns { Promise < Folder | null > } public async getFolder ( folderName ) { folderName = this . sanitizeFolderName ( folderName ) ; log . debug ( "getFolder" , folderName ) ; if ( folderName === "/" || folderName === "" ) { return this . getRootFolder ( ) ; } try { const stat = await this . stat ( folderName ) ; log . debug ( ": SUCCESS!!" ) ; if ( stat . type !== "file" ) { return new Folder ( this , stat . filename . replace ( /\\/g , "/" ) , stat . basename , stat . lastmod , stat . fileid ) ; } else { log . debug ( "getFolder: found object is file not a folder" ) ; return null ; } } catch ( e ) { log . debug ( "getFolder: exception occurred calling stat " , e . message ) ; return null ; } } param folderName returns public async getSubFolders ( folderName ) { log . debug ( "getSubFolders: folder " , folderName ) ; const folders = [ ] ; folderName = this . sanitizeFolderName ( folderName ) ; const folderElements = await this . contents ( folderName , true ) ; for ( const folderElement of folderElements ) { log . debug ( "getSubFolders: adding subfolders " , folderElement . filename ) ; folders . push ( new Folder ( this , folderElement . filename . replace ( /\\/g , "/" ) , folderElement . basename , folderElement . lastmod , folderElement . fileid ) ) ; } return folders ; } param { string } folderName param { FolderGetFilesOptions } options returns public async getFiles ( folderName , options ? ) { log . debug ( "getFiles: folder " , folderName ) ; const files = [ ] ; folderName = this . sanitizeFolderName ( folderName ) ; const fileElements = await this . contents ( folderName , false ) ; for ( const folderElement of fileElements ) { log . debug ( "getFiles: adding file " , folderElement . filename ) ; let file = new File ( this , folderElement . filename . replace ( /\\/g , "/" ) , folderElement . basename , folderElement . lastmod , folderElement . size , folderElement . mime , folderElement . fileid ) ; if ( options && options . filterFile ) { file = options . filterFile ( file ) ; } if ( file ) { files . push ( file ) ; } } return files ; } param fileName param data public async createFile ( fileName , data ) { if ( fileName . startsWith ( "./" ) ) { fileName = fileName . replace ( "./" , "/" ) ; } const baseName = path . basename ( fileName ) ; const folderName = path . dirname ( fileName ) ; log . debug ( `createFile folder name ${ folderName } base name ${ baseName }` ) ; await this . createFolder ( folderName ) ; await this . putFileContents ( fileName , data ) ; const file = await this . getFile ( fileName ) ; if ( ! file ) { throw new ClientError ( `Error creating file, file name "${ fileName }"` , "ERR_CREATE_FILE_FAILED" ) ; } return file ; } param fileName public async getFile ( fileName ) { log . debug ( "getFile fileName = " , fileName ) ; try { const stat = await this . stat ( fileName ) ; log . debug ( ": SUCCESS!!" ) ; if ( stat . type === "file" ) { return new File ( this , stat . filename . replace ( /\\/g , "/" ) , stat . basename , stat . lastmod , stat . size ! , stat . mime || "" , stat . fileid || - 1 ) ; } else { log . debug ( "getFile: found object is a folder not a file" ) ; return null ; } } catch ( e ) { log . debug ( "getFile: exception occurred calling stat " , e . message ) ; return null ; } } param sourceFileName param targetFileName public async moveFile ( sourceFileName , targetFileName ) { const url = this . webDAVUrl + sourceFileName ; const destinationUrl = this . webDAVUrl + targetFileName ; log . debug ( "moveFile from '" + url + "' to '" + destinationUrl + "'" ) ; const requestInit = { headers : new Headers ( { Destination : destinationUrl } ) , method : "MOVE" , } ; try { await this . getHttpResponse ( url , requestInit , [ 201 ] , { description : "File move" } ) ; } catch ( err ) { log . debug ( `Error in move file ${ err . message as string } ${ requestInit . method || "" } source: ${ url } destination: ${ destinationUrl }` ) ; throw new ClientError ( `Error: moving file failed: source=" ${ sourceFileName } target= ${ targetFileName } - ${ err . message as string }` , "ERR_FILE_MOVE_FAILED" ) ; } const targetFile = await this . getFile ( targetFileName ) ; if ( ! targetFile ) { throw new ClientError ( "Error: moving file failed: source=" + sourceFileName + " target=" + targetFileName , "ERR_FILE_MOVE_FAILED" ) ; } return targetFile ; } param sourceFolderName param tarName public async moveFolder ( sourceFolderName , tarName ) { const url = this . webDAVUrl + sourceFolderName ; const destinationUrl = this . webDAVUrl + tarName ; log . debug ( `moveFolder from '${ url }' to '${ destinationUrl }'` ) ; const requestInit = { headers : new Headers ( { Destination : destinationUrl } ) , method : "MOVE" , } ; try { await this . getHttpResponse ( url , requestInit , [ 201 ] , { description : "Folder move" } ) ; } catch ( err ) { log . debug ( `Error in move folder ${ err . message as string } ${ requestInit . method || "" } source: ${ url } destination: ${ destinationUrl }` ) ; throw new ClientError ( `Error: moving folder failed: source=${ sourceFolderName } target=${ tarName } - ${ err . message as string }` , "ERR_FOLDER_MOVE_FAILED" ) ; } const tar = await this . getFolder ( tarName ) ; if ( ! tar ) { throw new ClientError ( "Error: moving folder failed: source=" + sourceFolderName + " target=" + tarName , "ERR_FOLDER_MOVE_FAILED" ) ; } return tar ; } param fileName returns public async getContent ( fileName ) { const url = this . webDAVUrl + fileName ; log . debug ( "getContent GET " , url ) ; const requestInit = { method : "GET" , } ; let response ; try { response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : "File get content" } ) ; } catch ( err ) { log . debug ( `Error getContent ${ url } - error ${ err . message as string }` ) ; throw err ; } return Buffer . from ( await response . buffer ( ) ) ; } param fileName returns public async pipeContentStream ( fileName , destination ) { const url = this . webDAVUrl + fileName ; log . debug ( "getContent GET " , url ) ; const requestInit = { method : "GET" , } ; let response ; try { response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : "File pipe content stream" } ) ; } catch ( err ) { log . debug ( `Error getContent ${ url } - error ${ err . message as string }` ) ; throw err ; } response . body . pipe ( destination ) ; } param fileName returns public getLink ( fileName ) { log . debug ( "getLink of " , fileName ) ; return this . webDAVUrl + fileName ; } param fileId public getUILink ( fileId ) { log . debug ( "getUILink of " , fileId ) ; return `${ this . nextcloudOrigin }/apps/files/?fileid=${ fileId }` ; } param fileId param tagName returns throws public async addTagToFile ( fileId , tagName ) { log . debug ( `addTagToFile file: "${ fileId }" tag: "${ tagName }"` ) ; const tag = await this . createTag ( tagName ) ; if ( ! tag . canAssign ) { throw new ClientError ( `Error: No permission to assign tag "${ tagName }" to file. Tag is not assignable` , "ERR_TAG_NOT_ASSIGNABLE" ) ; } const addTagBody = { canAssign : tag . canAssign , id : tag . id , name : tag . name , userAssignable : tag . assignable , userVisible : tag . visible , } ; const requestInit = { body : JSON . stringify ( addTagBody , null , 4 ) , headers : new Headers ( { "Content-Type" : "application/json" } ) , method : "PUT" , } ; await this . getHttpResponse ( `${ this . nextcloudOrigin }/remote.php/dav/systemtags-relations/files/${ fileId }/${ tag . id }` , requestInit , [ 201 , 409 ] , { description : "File add tag" } ) ; } param fileId param comment public async addCommentToFile ( fileId , comment ) { log . debug ( `addCommentToFile file:"${ fileId }" comment:"${ comment }"` ) ; const addCommentBody = { actorType : "users" , message : comment , objectType : "files" , verb : "comment" , } ; const requestInit = { body : JSON . stringify ( addCommentBody , null , 4 ) , headers : new Headers ( { "Content-Type" : "application/json" } ) , method : "POST" , } ; await this . getHttpResponse ( `${ this . nextcloudOrigin }/remote.php/dav/comments/files/${ fileId }` , requestInit , [ 201 ] , { description : "File add comment" } ) ; } param fileId param top param skip returns throws public async getFileComments ( fileId , top ? , skip ? ) { log . debug ( "getFileComments fileId: " , fileId ) ; if ( ! top ) { top = 30 ; } if ( ! skip ) { skip = 0 ; } const requestInit = { body : `<?xml version="1.0" encoding="utf-8" ?>
                    <oc:filter-comments xmlns:D="DAV:" xmlns:oc="http://owncloud.org/ns">
                        <oc:limit>${ top }</oc:limit>
                        <oc:offset>${ skip }</oc:offset>
                    </oc:filter-comments>` , method : "REPORT" , } ; const response = await this . getHttpResponse ( `${ this . nextcloudOrigin }/remote.php/dav/comments/files/${ fileId }` , requestInit , [ 207 ] , { description : "File get comments" } ) ; const properties = await this . getPropertiesFromWebDAVMultistatusResponse ( response , "" ) ; const comments = [ ] ; for ( const prop of properties ) { comments . push ( prop . message ) ; } return comments ; } /**
     * returns system information about the nextcloud server and the nextcloud client
     */ public async getSystemInfo ( ) { const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; const response = await this . getHttpResponse ( this . nextcloudOrigin + "/ocs/v2.php/apps/serverinfo/api/v1/info" , requestInit , [ 200 ] , { description : "SystemInfo get" } ) ; const rawResult = await response . json ( ) ; let system = { } ; let storage = { } ; let shares = { } ; let server = { } ; let activeUsers = { } ; if ( rawResult && rawResult . ocs && rawResult . ocs . data ) { if ( rawResult . ocs . data . nextcloud ) { if ( rawResult . ocs . data . nextcloud . system ) { system = rawResult . ocs . data . nextcloud . system ; } else { throw new ClientError ( "Fatal Error: nextcloud data.nextcloud.system missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } if ( rawResult . ocs . data . nextcloud . storage ) { storage = rawResult . ocs . data . nextcloud . storage ; } else { throw new ClientError ( "Fatal Error: nextcloud data.nextcloud.storage missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } if ( rawResult . ocs . data . nextcloud . shares ) { shares = rawResult . ocs . data . nextcloud . shares ; } else { throw new ClientError ( "Fatal Error: nextcloud data.nextcloud.shares missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } } else { throw new ClientError ( "Fatal Error: nextcloud data.nextcloud missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } if ( rawResult . ocs . data . server ) { server = rawResult . ocs . data . server ; } else { throw new ClientError ( "Fatal Error: nextcloud data.server missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } if ( rawResult . ocs . data . activeUsers ) { activeUsers = rawResult . ocs . data . activeUsers ; } else { throw new ClientError ( "Fatal Error: nextcloud data.activeUsers missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } } else { throw new ClientError ( "Fatal Error: nextcloud system data missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } const result = { activeUsers , nextcloud : { shares , storage , system , } , nextcloudClient : { version : require ( "../package.json" ) . version , } , server , } ; return result ; } public async getSystemBasicData ( ) { const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; const response = await this . getHttpResponse ( this . nextcloudOrigin + "/ocs/v2.php/apps/serverinfo/api/v1/basicdata" , requestInit , [ 200 ] , { description : "System Basic Data get" } ) ; const rawResult = await response . json ( ) ; let result ; if ( rawResult && rawResult . ocs && rawResult . ocs . data && rawResult . ocs . data . servertime && rawResult . ocs . data . uptime && rawResult . ocs . data . timeservers ) { result = { serverTimeString : rawResult . ocs . data . servertime . replace ( "\n" , "" ) , uptimeString : rawResult . ocs . data . uptime . replace ( "\n" , "" ) , timeServersString : rawResult . ocs . data . timeservers . trim ( ) , } ; } else { throw new ClientError ( "Fatal Error: nextcloud basic data missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } return result ; } param search param limit param offset returns throws throws public async getUserGroups ( search ? , limit ? , offset ? ) { log . debug ( "getUserGroups" ) ; const userGroupIds = await this . getUserGroupIds ( search , limit , offset ) ; const userGroups = [ ] ; for ( const userGroupId of userGroupIds ) { userGroups . push ( new UserGroup ( this , userGroupId ) ) ; } return userGroups ; } param search param limit param offset returns throws throws public async getUserGroupIds ( search ? , limit ? , offset ? ) { log . debug ( "getUserGroupIds" ) ; const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; let url = this . getOcsUrl ( `/groups` ) ; const queryParameter = [ ] ; if ( search ) { queryParameter . push ( `search=${ search }` ) ; } if ( limit ) { if ( limit < 1 ) { throw new QueryLimitError ( "The limit must be larger than 0" ) ; } queryParameter . push ( `limit=${ limit }` ) ; } if ( offset ) { if ( offset < 1 ) { throw new QueryOffsetError ( "The offset must be larger than 0" ) ; } queryParameter . push ( `offset=${ offset }` ) ; } if ( queryParameter . join ( "&" ) . length > 1 ) { url += "?" + queryParameter . join ( "&" ) ; } log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : "User Groups get" } ) ; const rawResult = await response . json ( ) ; const userGroups = [ ] ; if ( rawResult . ocs && rawResult . ocs . data && rawResult . ocs . data . groups ) { log . debug ( "groups" , rawResult . ocs . data . groups ) ; rawResult . ocs . data . groups . forEach ( ( value ) => { userGroups . push ( value ) ; } ) ; } return userGroups ; } param id returns public async getUserGroup ( id ) { const userGroups = await this . getUserGroups ( id ) ; if ( userGroups [ 0 ] ) { return userGroups [ 0 ] ; } return null ; } param id returns throws public async getUserGroupMembers ( id ) { log . debug ( "getUserGroupMembers" ) ; const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; const url = this . getOcsUrl ( `/groups/${ id }` ) ; log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : "User group get members" } ) ; const rawResult = await response . json ( ) ; const userIds = [ ] ; if ( this . getOcsMetaStatus ( rawResult ) . code === 404 ) { throw new UserGroupDoesNotExistError ( `User Group ${ id } does not exist` ) ; } if ( rawResult . ocs && rawResult . ocs . data && rawResult . ocs . data . users ) { log . debug ( "members" , rawResult . ocs . data . users ) ; rawResult . ocs . data . users . forEach ( ( value ) => { userIds . push ( value ) ; } ) ; } return userIds ; } param id returns throws public async getUserGroupSubadmins ( id ) { log . debug ( "getUserGroupsubadmins" ) ; const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; const url = this . getOcsUrl ( `/groups/${ id }/subadmins` ) ; log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : "User group get subadmins" } ) ; const rawResult = await response . json ( ) ; const userIds = [ ] ; if ( this . getOcsMetaStatus ( rawResult ) . code === 101 ) { throw new UserGroupDoesNotExistError ( `User Group ${ id } does not exist` ) ; } if ( rawResult . ocs && rawResult . ocs . data ) { log . debug ( "subadmins" , rawResult . ocs . data ) ; rawResult . ocs . data . forEach ( ( value ) => { userIds . push ( value ) ; } ) ; } return userIds ; } async param { string } id returns { Promise < UserGroup > } throws public async createUserGroup ( id ) { log . debug ( "createUserGroup id=" , id ) ; const requestInit = { body : JSON . stringify ( { groupid : id } ) , headers : this . getOcsHeaders ( ) , method : "POST" , } ; log . debug ( "request body: " , requestInit . body ) ; const response = await this . getHttpResponse ( this . getOcsUrl ( `/groups` ) , requestInit , [ 200 ] , { description : "UserGroup create" } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 102 ) { throw new UserGroupAlreadyExistsError ( `User Group ${ id } already exists` ) ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 100 ) { return new UserGroup ( this , id ) ; } throw new OperationFailedError ( `User group ${ id } could not be created: ${ this . getOcsMetaStatus ( rawResult ) . message }` ) ; } param id returns { Promise < void > } throws throws public async deleteUserGroup ( id ) { log . debug ( "deleteUserGroup id=" , id ) ; const requestInit = { headers : this . getOcsHeaders ( ) , method : "DELETE" , } ; log . debug ( "request body: " , requestInit . body ) ; const response = await this . getHttpResponse ( this . getOcsUrl ( `/groups/${ id }` ) , requestInit , [ 200 ] , { description : "UserGroup delete" } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 101 ) { throw new UserGroupDoesNotExistError ( `User Group ${ id } does not exists` ) ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 102 ) { throw new UserGroupDeletionFailedError ( `User Group ${ id } could not be deleted` ) ; } } param search param limit param offset public async getUsers ( search ? , limit ? , offset ? ) { log . debug ( "getUsers" ) ; const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; let url = this . getOcsUrl ( `/users` ) ; const queryParameter = [ ] ; if ( search ) { queryParameter . push ( `search=${ search }` ) ; } if ( limit ) { if ( limit < 1 ) { throw new QueryLimitError ( "The limit must be larger than 0" ) ; } queryParameter . push ( `limit=${ limit }` ) ; } if ( offset ) { if ( offset < 1 ) { throw new QueryOffsetError ( "The offset must be larger than 0" ) ; } queryParameter . push ( `offset=${ offset }` ) ; } if ( queryParameter . join ( "&" ) . length > 1 ) { url += "?" + queryParameter . join ( "&" ) ; } log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : "Users get" } ) ; const rawResult = await response . json ( ) ; const users = [ ] ; if ( rawResult . ocs && rawResult . ocs . data && rawResult . ocs . data . users ) { log . debug ( "user ids" , rawResult . ocs . data . users ) ; rawResult . ocs . data . users . forEach ( ( value ) => { users . push ( new User ( this , value ) ) ; } ) ; } return users ; } param id returns throws public async getUserData ( id ) { log . debug ( "getUserData" ) ; const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; const url = this . getOcsUrl ( `/users/${ id }` ) ; log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : `User ${ id } get` } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 404 ) { throw new UserNotFoundError ( `User '${ id }' not found` ) ; } log . debug ( "user data" , rawResult . ocs . data ) ; const userData = { enabled : rawResult . ocs . data . enabled , lastLogin : rawResult . ocs . data . lastLogin === 0 ? undefined : new Date ( rawResult . ocs . data . lastLogin ) , subadminGroups : rawResult . ocs . data . subadmin , memberGroups : rawResult . ocs . data . groups , quota : { free : 0 , used : 0 , total : 0 , relative : 0 , quota : 0 , } , email : rawResult . ocs . data . email , displayName : rawResult . ocs . data . displayname , phone : rawResult . ocs . data . phone , address : rawResult . ocs . data . address , website : rawResult . ocs . data . website , twitter : rawResult . ocs . data . twitter , language : rawResult . ocs . data . language , locale : rawResult . ocs . data . locale , } ; if ( rawResult . ocs . data . quota . quota === "none" ) { userData . quota = { quota : 0 , relative : 0 , used : 0 } ; } else { if ( ! rawResult . ocs . data . quota . relative ) { rawResult . ocs . data . quota . relative = 0 ; } userData . quota = { quota : rawResult . ocs . data . quota . quota , relative : rawResult . ocs . data . quota . relative , used : rawResult . ocs . data . quota . used } ; if ( rawResult . ocs . data . quota . free ) { userData . quota . free = rawResult . ocs . data . quota . free ; } if ( rawResult . ocs . data . quota . total ) { userData . quota . total = rawResult . ocs . data . quota . total ; } } return userData ; } param id returns { Promise < void > } throws public async enableUser ( id ) { log . debug ( "enableUser" ) ; const requestInit = { headers : this . getOcsHeaders ( ) , method : "PUT" , } ; const url = this . getOcsUrl ( `/users/${ id }/enable` ) ; log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : `User ${ id } enable` } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 100 ) { return ; } throw new UserNotFoundError ( `User '${ id }' not found` ) ; } param id returns { Promise < void > } throws public async disableUser ( id ) { log . debug ( "disableUser" ) ; const requestInit = { headers : this . getOcsHeaders ( ) , method : "PUT" , } ; const url = this . getOcsUrl ( `/users/${ id }/disable` ) ; log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : `User ${ id } disable` } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 100 ) { return ; } throw new UserNotFoundError ( `User '${ id }' not found` ) ; } param id returns { Promise < void > } throws public async deleteUser ( id ) { log . debug ( "deleteUser" ) ; const requestInit = { headers : this . getOcsHeaders ( ) , method : "DELETE" , } ; const url = this . getOcsUrl ( `/users/${ id }` ) ; log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : `User ${ id } delete` } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 100 ) { return ; } throw new UserNotFoundError ( `User '${ id }' not found` ) ; } param id returns public async getUser ( id ) { log . debug ( "getUser" ) ; const users = await this . getUsers ( id ) ; if ( users [ 0 ] ) { return users [ 0 ] ; } return null ; } param options returns throws throws throws public async createUser ( options ) { log . debug ( "createUser" ) ; const createUserBody = { userid : options . id } ; if ( options . email ) { if ( /^[^\s@]+@[^\s@]+\.[^\s@]+$/ . test ( options . email ) ) { createUserBody . email = options . email ; } else { throw new UserCreateError ( `Error creating user '${ options . id }' - invalid email address '${ options . email }'` ) ; } } if ( options . password ) { createUserBody . password = options . password ; } const requestInit = { body : JSON . stringify ( createUserBody , null , 4 ) , headers : this . getOcsHeaders ( ) , method : "POST" , } ; log . debug ( "request body: " , requestInit . body ) ; const response = await this . getHttpResponse ( this . getOcsUrl ( `/users` ) , requestInit , [ 200 ] , { description : `User ${ options . id } create` } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 102 ) { throw new UserAlreadyExistsError ( `User with id '${ options . id }' already exists` ) ; } const user = await this . getUser ( options . id ) ; if ( user ) { return user ; } throw new UserCreateError ( `Error creating user '${ options . id }' - ${ this . getOcsMetaStatus ( rawResult ) . message } (${ this . getOcsMetaStatus ( rawResult ) . code })` ) ; } async param { string } id param { UserProperty } property param { string } value returns { Promise < void > } throws throws public async updateUserProperty ( id , property , value ) { log . debug ( "updateUserProperty" ) ; const body = { key : property , value } ; const requestInit = { body : JSON . stringify ( body , null , 4 ) , headers : this . getOcsHeaders ( ) , method : "PUT" , } ; const url = this . getOcsUrl ( `/users/${ id }` ) ; log . debug ( "request body: " , requestInit . body ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 , 401 ] , { description : `User ${ id } update ${ property }=${ value }` } ) ; const rawResult = await response . json ( ) ; if ( response . status === 401 ) { throw new UserNotFoundError ( `User with id '${ id }' not found` ) ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 100 ) { return ; } if ( property === UserProperty . password ) { value = "********" ; } throw new UserUpdateError ( `User with id '${ id }' could not be updated - ${ property }=${ value }. ${ rawResult . ocs . meta . message as string }` ) ; } param id throws public async resendWelcomeEmail ( id ) { log . debug ( "resendWelcomeEmail" ) ; const requestInit = { headers : this . getOcsHeaders ( ) , method : "POST" , } ; const url = this . getOcsUrl ( `/users/${ id }/welcome` ) ; log . debug ( "request body: " , requestInit . body ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : `Resend welcome email for user ${ id }` } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 101 ) { throw new UserResendWelcomeEmailError ( `Error sending welcome email for '${ id }': Email address not available` ) ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 100 ) { return ; } throw new UserResendWelcomeEmailError ( `Error sending welcome email for '${ id }' failed` ) ; } param id param userGroupId returns { Promise < void > } throws throws throws throws public async addUserToMemberUserGroup ( id , userGroupId ) { log . debug ( "addUserToUserGroup" ) ; const body = { groupid : userGroupId } ; const requestInit = { body : JSON . stringify ( body , null , 4 ) , headers : this . getOcsHeaders ( ) , method : "POST" , } ; const url = this . getOcsUrl ( `/users/${ id }/groups` ) ; log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : `Add user ${ id } to user group ${ userGroupId }` } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 100 ) { return ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 102 ) { throw new UserGroupDoesNotExistError ( `User group ${ userGroupId } does not exist` ) ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 103 ) { throw new UserNotFoundError ( `User ${ id } does not exist` ) ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 104 ) { throw new InsufficientPrivilegesError ( `Insufficient privileges to add a user to a group` ) ; } throw new OperationFailedError ( `User ${ id } could not be added to user group ${ userGroupId }: ${ this . getOcsMetaStatus ( rawResult ) . message }` ) ; } param id param userGroupId returns { Promise < void > } throws throws throws throws public async removeUserFromMemberUserGroup ( id , userGroupId ) { log . debug ( "removeUserFromMemberUserGroup" ) ; const body = { groupid : userGroupId } ; const requestInit = { body : JSON . stringify ( body , null , 4 ) , headers : this . getOcsHeaders ( ) , method : "DELETE" , } ; const url = this . getOcsUrl ( `/users/${ id }/groups` ) ; log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : `Remove user ${ id } from user group ${ userGroupId }` } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 100 ) { return ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 102 ) { throw new UserGroupDoesNotExistError ( `User group ${ userGroupId } does not exist` ) ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 103 ) { throw new UserNotFoundError ( `User ${ id } does not exist` ) ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 104 ) { throw new InsufficientPrivilegesError ( `Insufficient privileges to add a user to a group` ) ; } throw new OperationFailedError ( `User ${ id } could not be added to user group ${ userGroupId }: ${ this . getOcsMetaStatus ( rawResult ) . message }` ) ; } param id param userGroupId returns { Promise < void > } throws throws throws throws public async promoteUserToUserGroupSubadmin ( id , userGroupId ) { log . debug ( "promoteUserToUserGroupSubadmin" ) ; const body = { groupid : userGroupId } ; const requestInit = { body : JSON . stringify ( body , null , 4 ) , headers : this . getOcsHeaders ( ) , method : "POST" , } ; const url = this . getOcsUrl ( `/users/${ id }/subadmins` ) ; log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : `Promote User ${ id } to user group subadmin ${ userGroupId }` } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 100 ) { return ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 102 ) { throw new UserGroupDoesNotExistError ( `User group ${ userGroupId } does not exist` ) ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 101 ) { throw new UserNotFoundError ( `User ${ id } does not exist` ) ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 104 ) { throw new InsufficientPrivilegesError ( `Insufficient privileges to add a user to a group` ) ; } throw new OperationFailedError ( `User ${ id } could not be removed from user group ${ userGroupId }: ${ this . getOcsMetaStatus ( rawResult ) . message }` ) ; } param id param userGroupId returns { Promise < void > } throws throws public async demoteUserFromSubadminUserGroup ( id , userGroupId ) { log . debug ( "demoteUserFromSubadminUserGroup" ) ; const body = { groupid : userGroupId } ; const requestInit = { body : JSON . stringify ( body , null , 4 ) , headers : this . getOcsHeaders ( ) , method : "DELETE" , } ; const url = this . getOcsUrl ( `/users/${ id }/subadmins` ) ; log . debug ( "url " , url ) ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : `Demotes user ${ id } from subadmin user group ${ userGroupId }` } ) ; const rawResult = await response . json ( ) ; if ( this . getOcsMetaStatus ( rawResult ) . code === 100 ) { return ; } if ( this . getOcsMetaStatus ( rawResult ) . code === 104 ) { throw new InsufficientPrivilegesError ( `Insufficient privileges to add a user to a group` ) ; } throw new OperationFailedError ( `User ${ id } could not be demoted from subadmin user group ${ userGroupId }: ${ this . getOcsMetaStatus ( rawResult ) . message }` ) ; } param options returns public async upsertUsers ( options ) { const report = [ ] ; for ( const option of options ) { const userReport = { id : option . id , message : "" , changes : [ ] } ; let user = await this . getUser ( option . id ) ; if ( ! user ) { try { user = await this . createUser ( { id : option . id , email : option . email , password : option . password } ) ; userReport . message = `User ${ option . id } created` ; } catch ( e ) { userReport . message = `Create user ${ option . id } failed ${ e . message as string }` ; report . push ( userReport ) ; continue ; } } else { userReport . message = `User ${ option . id } changed` ; } let previousValue = "" ; let newValue = "" ; let property = "" ; if ( option . enabled !== undefined ) { if ( ( await user . isEnabled ( ) ) && option . enabled === false ) { try { await user . disable ( ) ; userReport . changes . push ( { property : "enabled" , previousValue : "true" , newValue : "false" } ) ; } catch ( e ) { userReport . changes . push ( { property : "enabled" , previousValue : "true" , newValue : "true" , error : e . message } ) ; } } if ( ( await user . isEnabled ( ) ) === false && option . enabled === true ) { try { await user . enable ( ) ; userReport . changes . push ( { property : "enabled" , previousValue : "false" , newValue : "true" } ) ; } catch ( e ) { userReport . changes . push ( { property : "enabled" , previousValue : "false" , newValue : "false" , error : e . message } ) ; } } } if ( option . superAdmin !== undefined ) { if ( ( await user . isSuperAdmin ( ) ) && option . superAdmin === false ) { try { await user . demoteFromSuperAdmin ( ) ; userReport . changes . push ( { property : "superAdmin" , previousValue : "true" , newValue : "false" } ) ; } catch ( e ) { userReport . changes . push ( { property : "superAdmin" , previousValue : "true" , newValue : "true" , error : e . message } ) ; } } if ( ( await user . isSuperAdmin ( ) ) === false && option . superAdmin === true ) { try { await user . promoteToSuperAdmin ( ) ; userReport . changes . push ( { property : "superAdmin" , previousValue : "false" , newValue : "true" } ) ; } catch ( e ) { userReport . changes . push ( { property : "superAdmin" , previousValue : "false" , newValue : "false" , error : e . message } ) ; } } } if ( option . memberGroups !== undefined ) { const previousGroups = await user . getMemberUserGroupIds ( ) ; const newGroups = option . memberGroups ; if ( option . superAdmin !== undefined ) { if ( option . superAdmin === true ) { if ( newGroups . indexOf ( "admin" ) === - 1 ) { newGroups . push ( "admin" ) ; } } } const groupsToAdd = newGroups . filter ( ( x ) => ! previousGroups . includes ( x ) ) ; const groupsToRemove = previousGroups . filter ( ( x ) => ! newGroups . includes ( x ) ) ; let userGroup ; property = "memberGroups" ; let error = null ; for ( const groupId of groupsToAdd ) { userGroup = await this . getUserGroup ( groupId ) ; if ( ! userGroup ) { try { userGroup = await this . createUserGroup ( groupId ) ; } catch ( e ) { error = e as Error ; break ; } } try { await user . addToMemberUserGroup ( userGroup ) ; } catch ( e ) { error = e as Error ; break ; } } for ( const groupId of groupsToRemove ) { try { await user . removeFromMemberUserGroup ( new UserGroup ( this , groupId ) ) ; } catch ( e ) { error = e as Error ; break ; } } if ( error ) { userReport . changes . push ( { property , previousValue : previousGroups . join ( ", " ) , newValue : previousGroups . join ( ", " ) , error : error . message } ) ; } else { if ( groupsToAdd . length > 0 || groupsToRemove . length > 0 ) { userReport . changes . push ( { property , previousValue : previousGroups . join ( ", " ) , newValue : newGroups . join ( ", " ) } ) ; } } } if ( option . subadminGroups !== undefined ) { const previousGroups = await user . getSubadminUserGroupIds ( ) ; const newGroups = option . subadminGroups ; const groupsToAdd = newGroups . filter ( ( x ) => ! previousGroups . includes ( x ) ) ; const groupsToRemove = previousGroups . filter ( ( x ) => ! newGroups . includes ( x ) ) ; let userGroup ; property = "subadminGroups" ; let error = null ; for ( const groupId of groupsToAdd ) { userGroup = await this . getUserGroup ( groupId ) ; if ( ! userGroup ) { try { userGroup = await this . createUserGroup ( groupId ) ; } catch ( e ) { error = e as Error ; break ; } } try { await user . promoteToUserGroupSubadmin ( userGroup ) ; } catch ( e ) { error = e as Error ; break ; } } for ( const groupId of groupsToRemove ) { try { await user . demoteFromSubadminUserGroup ( new UserGroup ( this , groupId ) ) ; } catch ( e ) { error = e as Error ; break ; } } if ( error ) { userReport . changes . push ( { property , previousValue : previousGroups . join ( ", " ) , newValue : previousGroups . join ( ", " ) , error : error . message } ) ; } else { userReport . changes . push ( { property , previousValue : previousGroups . join ( ", " ) , newValue : newGroups . join ( ", " ) } ) ; } } if ( option . displayName !== undefined ) { previousValue = await user . getDisplayName ( ) ; newValue = option . displayName ; property = "displayName" ; if ( previousValue !== newValue ) { try { await user . setDisplayName ( option . displayName ) ; userReport . changes . push ( { property , previousValue , newValue } ) ; } catch ( e ) { let message = "" ; if ( e instanceof Error ) { message = e . message ; } userReport . changes . push ( { property , previousValue , newValue : previousValue , error : message } ) ; } } } if ( option . email !== undefined ) { previousValue = await user . getEmail ( ) ; newValue = option . email ; property = "email" ; if ( previousValue !== newValue ) { try { await user . setEmail ( option . email ) ; userReport . changes . push ( { property , previousValue , newValue } ) ; } catch ( e ) { let message = "" ; if ( e instanceof Error ) { message = e . message ; } userReport . changes . push ( { property , previousValue , newValue : previousValue , error : message } ) ; } } } if ( option . language !== undefined ) { previousValue = await user . getLanguage ( ) ; newValue = option . language ; property = "language" ; if ( previousValue !== newValue ) { try { await user . setLanguage ( option . language ) ; userReport . changes . push ( { property , previousValue , newValue } ) ; } catch ( e ) { let message = "" ; if ( e instanceof Error ) { message = e . message ; } userReport . changes . push ( { property , previousValue , newValue : previousValue , error : message } ) ; } } } if ( option . locale !== undefined ) { previousValue = await user . getLocale ( ) ; newValue = option . locale ; property = "locale" ; if ( previousValue !== newValue ) { try { await user . setLocale ( option . locale ) ; userReport . changes . push ( { property , previousValue , newValue } ) ; } catch ( e ) { let message = "" ; if ( e instanceof Error ) { message = e . message ; } userReport . changes . push ( { property , previousValue , newValue : previousValue , error : message } ) ; } } } if ( option . twitter !== undefined ) { previousValue = await user . getTwitter ( ) ; newValue = option . twitter ; property = "twitter" ; if ( previousValue !== newValue ) { try { await user . setTwitter ( option . twitter ) ; userReport . changes . push ( { property , previousValue , newValue } ) ; } catch ( e ) { let message = "" ; if ( e instanceof Error ) { message = e . message ; } userReport . changes . push ( { property , previousValue , newValue : previousValue , error : message } ) ; } } } if ( option . phone !== undefined ) { previousValue = await user . getPhone ( ) ; newValue = option . phone ; property = "phone" ; if ( previousValue !== newValue ) { try { await user . setPhone ( option . phone ) ; userReport . changes . push ( { property , previousValue , newValue } ) ; } catch ( e ) { let message = "" ; if ( e instanceof Error ) { message = e . message ; } userReport . changes . push ( { property , previousValue , newValue : previousValue , error : message } ) ; } } } if ( option . password !== undefined ) { previousValue = "********" ; newValue = option . password ; property = "password" ; try { await user . setPassword ( option . password ) ; userReport . changes . push ( { property , previousValue , newValue : previousValue } ) ; } catch ( e ) { let message = "" ; if ( e instanceof Error ) { message = e . message ; } userReport . changes . push ( { property , previousValue , newValue : previousValue , error : message } ) ; } } if ( option . address !== undefined ) { previousValue = await user . getAddress ( ) ; newValue = option . address ; property = "address" ; if ( previousValue !== newValue ) { try { await user . setAddress ( option . address ) ; userReport . changes . push ( { property , previousValue , newValue } ) ; } catch ( e ) { let message = "" ; if ( e instanceof Error ) { message = e . message ; } userReport . changes . push ( { property , previousValue , newValue : previousValue , error : message } ) ; } } } if ( option . website !== undefined ) { previousValue = await user . getWebsite ( ) ; newValue = option . website ; property = "website" ; if ( previousValue !== newValue ) { try { await user . setWebsite ( option . website ) ; userReport . changes . push ( { property , previousValue , newValue } ) ; } catch ( e ) { let message = "" ; if ( e instanceof Error ) { message = e . message ; } userReport . changes . push ( { property , previousValue , newValue : previousValue , error : message } ) ; } } } if ( option . quota !== undefined ) { previousValue = ( await user . getQuotaUserFriendly ( ) ) . quota ; newValue = option . quota ; property = "quota" ; if ( previousValue !== newValue ) { try { await user . setQuota ( option . quota ) ; userReport . changes . push ( { property , previousValue , newValue } ) ; } catch ( e ) { let message = "" ; if ( e instanceof Error ) { message = e . message ; } userReport . changes . push ( { property , previousValue , newValue : previousValue , error : message } ) ; } } } if ( option . resendWelcomeEmail !== undefined ) { previousValue = "not sent" ; newValue = "sent" ; property = "resendWelcomeEmail" ; if ( option . resendWelcomeEmail ) { try { await user . resendWelcomeEmail ( ) ; userReport . changes . push ( { property , previousValue , newValue } ) ; } catch ( e ) { let message = "" ; if ( e instanceof Error ) { message = e . message ; } userReport . changes . push ( { property , previousValue , newValue : previousValue , error : message } ) ; } } } if ( userReport . changes . length === 0 ) { userReport . message = `User ${ option . id } not changed` ; } report . push ( userReport ) ; } return report ; } /**
     * create a new share
     */ public async createShare ( options ) { const shareRequest = Share . createShareRequestBody ( options ) ; log . debug ( shareRequest ) ; const requestInit = { body : shareRequest , headers : this . getOcsHeaders ( ) , method : "POST" , } ; const url = this . nextcloudOrigin + "/ocs/v2.php/apps/files_sharing/api/v1/shares" ; const response = await this . getHttpResponse ( url , requestInit , [ 200 , 204 ] , { description : "Share create" } ) ; const rawResult = ( await response . json ( ) ) as { ocs : { data : { id : string ; } ; } ; } ; log . debug ( rawResult ) ; const share = await Share . getShare ( this , rawResult . ocs . data . id ) ; if ( options . publicUpload ) { await share . setPublicUpload ( ) ; } return share ; } /**
     * update a new share
     */ public async updateShare ( shareId , body ) { log . debug ( "updateShare body " , body ) ; const requestInit = { body : JSON . stringify ( body , null , 4 ) , headers : this . getOcsHeaders ( ) , method : "PUT" , } ; const url = this . nextcloudOrigin + "/ocs/v2.php/apps/files_sharing/api/v1/shares/" + shareId ; await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : "Share update" } ) ; } param shareId public async getShare ( shareId ) { const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; const url = this . nextcloudOrigin + "/ocs/v2.php/apps/files_sharing/api/v1/shares/" + shareId ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : "Share get" } ) ; return await response . json ( ) ; } param shareId public async deleteShare ( shareId ) { const requestInit = { headers : this . getOcsHeaders ( ) , method : "DELETE" , } ; const url = this . nextcloudOrigin + "/ocs/v2.php/apps/files_sharing/api/v1/shares/" + shareId ; const response = await this . getHttpResponse ( url , requestInit , [ 200 ] , { description : "Share delete" } ) ; } returns public async getNotifications ( ) { const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; const response = await this . getHttpResponse ( this . nextcloudOrigin + "/ocs/v2.php/apps/notifications/api/v2/notifications" , requestInit , [ 200 , 404 ] , { description : "Notifications get" } ) ; if ( response . status === 404 ) { return [ ] ; } const rawResult = await response . json ( ) ; let notifications = [ ] ; if ( rawResult && rawResult . ocs && rawResult . ocs . data ) { notifications = rawResult . ocs . data ; } else { throw new ClientError ( "Fatal Error: nextcloud notifications data missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } const result = notifications ; return result ; } public async getUpdateNotifications ( version ) { const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; const response = await this . getHttpResponse ( this . nextcloudOrigin + `/ocs/v2.php/apps/updatenotification/api/v1/applist/${ version }` , requestInit , [ 200 ] , { description : "UpdateNotifications get" } ) ; const rawResult = await response . json ( ) ; let updateNotification = { } ; if ( rawResult && rawResult . ocs && rawResult . ocs . data ) { updateNotification = rawResult . ocs . data ; } else { throw new ClientError ( "Fatal Error: nextcloud notifications data missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } const result = updateNotification ; return result ; } public async sendNotificationToUser ( userId , shortMessage , longMessage ? ) { const requestInit = { headers : new Headers ( { Accept : "application/json" , "Content-Type" : "application/x-www-form-urlencoded" , "OCS-APIRequest" : "true" , } ) , method : "POST" , } ; if ( ! longMessage ) { longMessage = "" ; } longMessage = `&longMessage=${ encodeURIComponent ( longMessage ) }` ; const queryString = `${ encodeURIComponent ( userId ) }?shortMessage=${ encodeURIComponent ( shortMessage ) }${ longMessage }` ; await this . getHttpResponse ( this . nextcloudOrigin + `/ocs/v2.php/apps/admin_notifications/api/v1/notifications/${ queryString }` , requestInit , [ 200 ] , { description : "User create" } ) ; } /**
     * returns apps
     */ public async getApps ( ) { const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; const response = await this . getHttpResponse ( this . getOcsUrl ( `/apps` ) , requestInit , [ 200 ] , { description : "Apps get" } ) ; const rawResult = await response . json ( ) ; let apps = [ ] ; if ( rawResult && rawResult . ocs && rawResult . ocs . data ) { apps = rawResult . ocs . data ; } else { throw new ClientError ( "Fatal Error: nextcloud apps data missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } const result = apps ; return result ; } public async getAppInfos ( appName ) { const requestInit = { headers : this . getOcsHeaders ( ) , method : "GET" , } ; const response = await this . getHttpResponse ( this . getOcsUrl ( `/apps/${ appName }` ) , requestInit , [ 200 ] , { description : "App Infos get" } ) ; const rawResult = await response . json ( ) ; let appInfo = { } ; if ( rawResult && rawResult . ocs && rawResult . ocs . data ) { appInfo = rawResult . ocs . data ; } else { throw new ClientError ( "Fatal Error: nextcloud apps data missing" , "ERR_SYSTEM_INFO_MISSING_DATA" ) ; } const result = appInfo ; return result ; } param response param href returns throws private async getPropertiesFromWebDAVMultistatusResponse ( response , href ) { const responseContentType = response . headers . get ( "Content-Type" ) ; if ( ! responseContentType ) { throw new ClientError ( "Response content type expected" , "ERR_RESPONSE_WITHOUT_CONTENT_TYPE_HEADER" ) ; } if ( responseContentType . indexOf ( "application/xml" ) === - 1 ) { throw new ClientError ( "XML response content type expected" , "ERR_XML_RESPONSE_CONTENT_TYPE_EXPECTED" ) ; } const xmlBody = await response . text ( ) ; if ( parser . validate ( xmlBody ) !== true ) { throw new ClientError ( `The response is not valid XML: ${ xmlBody }` , "ERR_RESPONSE_NOT_INVALID_XML" ) ; } const options = { ignoreNameSpace : true , } ; const body = parser . parse ( xmlBody , options ) ; if ( ! body . multistatus || ! body . multistatus . response ) { throw new ClientError ( `The response is is not a WebDAV multistatus response` , "ERR_RESPONSE_NO_MULTISTATUS_XML" ) ; } if ( body . multistatus . response . href || body . multistatus . response . propstat ) { body . multistatus . response = new Array ( body . multistatus . response ) ; } const responseProperties = [ ] ; for ( const res of body . multistatus . response ) { if ( ! res . href ) { throw new ClientError ( `The mulitstatus response must have a href` , "ERR_RESPONSE_MISSING_HREF_MULTISTATUS" ) ; } if ( ! res . propstat ) { throw new ClientError ( `The mulitstatus response must have a "propstat" container` , "ERR_RESPONSE_MISSING_PROPSTAT" ) ; } let propStats = res . propstat ; if ( res . propstat . status || res . propstat . prop ) { propStats = [ res . propstat ] ; } for ( const propStat of propStats ) { if ( ! propStat . status ) { throw new ClientError ( `The propstat must have a "status"` , "ERR_RESPONSE_MISSING_PROPSTAT_STATUS" ) ; } if ( propStat . status === "HTTP/1.1 200 OK" ) { if ( ! propStat . prop ) { throw new ClientError ( `The propstat must have a "prop"` , "ERR_RESPONSE_MISSING_PROPSTAT_PROP" ) ; } const property = propStat . prop ; property . _href = res . href ; responseProperties . push ( property ) ; } } } return responseProperties ; } returns private async getHttpResponse ( url , requestInit , expectedHttpStatusCode , context ) { if ( ! requestInit . headers ) { requestInit . headers = new Headers ( ) ; } if ( this . fakeServer ) { return await this . fakeServer . getFakeHttpResponse ( url , requestInit , expectedHttpStatusCode , context ) ; } else { return await this . httpClient ! . getHttpResponse ( url , requestInit , expectedHttpStatusCode , context ) ; } } param folderName param folderIndicator returns private async contents ( folderName , folderIndicator ) { log . debug ( "Contents: folder " , folderName ) ; const folders = [ ] ; folderName = this . sanitizeFolderName ( folderName ) ; const resultArray = [ ] ; if ( folderIndicator === true ) { log . debug ( "Contents: get folders" ) ; } else { log . debug ( "Contents: get files" ) ; } try { const folderContentsArray = await this . getFolderContents ( folderName ) ; for ( const folderElement of folderContentsArray ) { if ( folderElement . type === "directory" ) { if ( folderIndicator === true ) { resultArray . push ( folderElement ) ; } } else { if ( folderIndicator === false ) { log . debug ( "Contents folder element file " , folderElement ) ; resultArray . push ( folderElement ) ; } } } } catch ( e ) { log . debug ( "Contents: exception occurred " , e . message ) ; } return resultArray ; } private sanitizeFolderName ( folderName ) { if ( folderName [ 0 ] !== "/" ) { folderName = "/" + folderName ; } folderName = folderName . replace ( /\/+$/ , "" ) ; if ( folderName === "" ) { folderName = "/" ; } return folderName ; } private getTagIdFromHref ( href ) { return parseInt ( href . split ( "/" ) [ href . split ( "/" ) . length - 1 ] , 10 ) ; } private async createFolderInternal ( folderName ) { const url = this . webDAVUrl + folderName ; log . debug ( "createFolderInternal " , url ) ; const requestInit = { method : "MKCOL" , } ; try { await this . getHttpResponse ( url , requestInit , [ 201 ] , { description : "Folder create" } ) ; } catch ( err ) { log . debug ( `Error in createFolderInternal ${ err . message as string } ${ requestInit . method || "" } ${ url }` ) ; throw err ; } } private async stat ( fileName ) { const url = this . webDAVUrl + fileName ; log . debug ( "stat " , url ) ; const requestInit = { body : `<?xml version="1.0"?>
            <d:propfind  xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
            <d:prop>
                  <d:getlastmodified />
                  <d:getetag />
                  <d:getcontenttype />
                  <d:resourcetype />
                  <oc:fileid />
                  <oc:permissions />
                  <oc:size />
                  <d:getcontentlength />
                  <nc:has-preview />
                  <oc:favorite />
                  <oc:comments-unread />
                  <oc:owner-display-name />
                  <oc:share-types />
            </d:prop>
          </d:propfind>` , headers : new Headers ( ) , method : "PROPFIND" , } ; let response ; try { response = await this . getHttpResponse ( url , requestInit , [ 207 ] , { description : "File/Folder get details" } ) ; } catch ( err ) { log . debug ( `Error in stat ${ err . message as string } ${ requestInit . method || "" } ${ url }` ) ; throw err ; } const properties = await this . getPropertiesFromWebDAVMultistatusResponse ( response , "" ) ; let resultStat = null ; for ( const prop of properties ) { resultStat = { basename : basename ( fileName ) , fileid : prop . fileid , filename : fileName , lastmod : prop . getlastmodified , type : "file" , } ; if ( prop . getcontentlength ) { resultStat . size = prop . getcontentlength ; } else { resultStat . type = "directory" ; } if ( prop . getcontenttype ) { resultStat . mime = prop . getcontenttype ; } } if ( ! resultStat ) { log . debug ( "Error: response " , JSON . stringify ( properties , null , 4 ) ) ; throw new ClientError ( "Error getting status information from : " + url , "ERR_STAT" ) ; } return resultStat ; } private getOcsMetaStatus ( input ) { let code ; let message = "" ; if ( input . ocs && input . ocs . meta && input . ocs . meta . statuscode ) { code = input . ocs . meta . statuscode ; if ( input . ocs . meta . message ) { message = input . ocs . meta . message ; } return { code , message } ; } throw new InvalidServiceResponseFormatError ( "Fatal Error: The OCS meta status could not be retrieved from OCS response" ) ; } private getOcsHeaders ( ) { return new Headers ( { "OCS-APIRequest" : "true" , "Content-Type" : "application/json" , Accept : "application/json" , } ) ; } private getOcsUrl ( suffix ) { return `${ this . nextcloudOrigin }/ocs/v1.php/cloud${ suffix }` ; } private async putFileContents ( fileName , data ) { const url = this . webDAVUrl + fileName ; log . debug ( "putFileContents " , url ) ; const requestInit = { body : data , method : "PUT" , } ; let description = "File save content " ; if ( data instanceof Buffer ) { description += "from buffer" ; } else { description += "from stream" ; } return await this . getHttpResponse ( url , requestInit , [ 201 , 204 ] , { description } ) ; } }