Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(files): update mtime on attributes tampering #602

Merged
merged 1 commit into from Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
76 changes: 73 additions & 3 deletions __tests__/files/file.spec.ts
Expand Up @@ -7,7 +7,9 @@ describe('File creation', () => {
const file = new File({
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
mime: 'image/jpeg',
owner: 'emma'
owner: 'emma',
mtime: new Date(Date.UTC(2023, 0, 1, 0, 0, 0)),
crtime: new Date(Date.UTC(1990, 0, 1, 0, 0, 0)),
})

expect(file).toBeInstanceOf(File)
Expand All @@ -19,6 +21,10 @@ describe('File creation', () => {
expect(file.size).toBeUndefined()
expect(file.attributes).toStrictEqual({})

// Times
expect(file.mtime?.toISOString()).toBe('2023-01-01T00:00:00.000Z')
expect(file.crtime?.toISOString()).toBe('1990-01-01T00:00:00.000Z')

// path checks
expect(file.basename).toBe('picture.jpg')
expect(file.extension).toBe('.jpg')
Expand Down Expand Up @@ -87,38 +93,48 @@ describe('File data change', () => {
const file = new File({
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
mime: 'image/jpeg',
owner: 'emma'
owner: 'emma',
mtime: new Date(Date.UTC(2023, 0, 1, 0, 0, 0)),
})

expect(file.basename).toBe('picture.jpg')
expect(file.dirname).toBe('/')
expect(file.root).toBe('/files/emma/Photos')
expect(file.mtime?.toISOString()).toBe('2023-01-01T00:00:00.000Z')

file.rename('picture-old.jpg')

expect(file.basename).toBe('picture-old.jpg')
expect(file.dirname).toBe('/')
expect(file.source).toBe('https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture-old.jpg')
expect(file.root).toBe('/files/emma/Photos')

// Check that mtime has been updated
expect(file.mtime?.getDate()).toBe(new Date().getDate())
})

test('Moving a file', () => {
const file = new File({
source: 'https://cloud.domain.com/remote.php/dav/files/emma/Photos/picture.jpg',
mime: 'image/jpeg',
owner: 'emma'
owner: 'emma',
mtime: new Date(Date.UTC(2023, 0, 1, 0, 0, 0)),
})

expect(file.basename).toBe('picture.jpg')
expect(file.dirname).toBe('/')
expect(file.root).toBe('/files/emma/Photos')
expect(file.mtime?.toISOString()).toBe('2023-01-01T00:00:00.000Z')

file.move('https://cloud.domain.com/remote.php/dav/files/emma/Pictures/picture-old.jpg')

expect(file.basename).toBe('picture-old.jpg')
expect(file.dirname).toBe('/')
expect(file.source).toBe('https://cloud.domain.com/remote.php/dav/files/emma/Pictures/picture-old.jpg')
expect(file.root).toBe('/files/emma/Pictures')

// Check that mtime has been updated
expect(file.mtime?.getDate()).toBe(new Date().getDate())
})

test('Moving a file to a different folder with root', () => {
Expand All @@ -141,3 +157,57 @@ describe('File data change', () => {
expect(file.root).toBe('/files/emma')
})
})


describe('Altering attributes updates mtime', () => {
test('mtime is updated on existing attribute', () => {
const file = new File({
source: 'https://cloud.domain.com/remote.php/dav/files/emma',
mime: 'image/jpeg',
owner: 'emma',
mtime: new Date(Date.UTC(1990, 0, 1, 0, 0, 0)),
attributes: {
test: true
},
})
expect(file.attributes.test).toBe(true)
file.attributes.test = false

// Check that mtime has been updated
expect(file.mtime?.getDate()).toBe(new Date().getDate())
expect(file.attributes.test).toBe(false)
})

test('mtime is updated on new attribute', () => {
const file = new File({
source: 'https://cloud.domain.com/remote.php/dav/files/emma',
mime: 'image/jpeg',
owner: 'emma',
mtime: new Date(Date.UTC(1990, 0, 1, 0, 0, 0)),
})
expect(file.attributes.test).toBeFalsy()
file.attributes.test = true

// Check that mtime has been updated
expect(file.mtime?.getDate()).toBe(new Date().getDate())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test will fail occasionally if the previous statement runs a microsecond earlier than the next invocation of new Date

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No because I'm comparing the date, so it would have to be exactly 1ms before midnight 😁

expect(file.attributes.test).toBe(true)
})

test('mtime is updated on deleted attribute', () => {
const file = new File({
source: 'https://cloud.domain.com/remote.php/dav/files/emma',
mime: 'image/jpeg',
owner: 'emma',
mtime: new Date(Date.UTC(1990, 0, 1, 0, 0, 0)),
attributes: {
test: true
},
})
expect(file.attributes.test).toBe(true)
delete file.attributes.test

// Check that mtime has been updated
expect(file.mtime?.getDate()).toBe(new Date().getDate())
expect(file.attributes.test).toBeUndefined()
})
})
2 changes: 1 addition & 1 deletion __tests__/files/node.spec.ts
Expand Up @@ -252,4 +252,4 @@ describe('Root and paths detection', () => {
})
expect(file1.dirname).toBe('/')
})
})
})
37 changes: 35 additions & 2 deletions lib/files/node.ts
Expand Up @@ -24,17 +24,35 @@ import { Permission } from '../permissions'
import { FileType } from './fileType'
import NodeData, { Attribute, validateData } from './nodeData'


export abstract class Node {
private _data: NodeData
private _attributes: Attribute[]
private _attributes: Attribute
private _knownDavService = /(remote|public)\.php\/(web)?dav/i

constructor(data: NodeData, davService?: RegExp) {
// Validate data
validateData(data)

this._data = data
this._attributes = data.attributes || {} as any

const handler = {
set: (target: Attribute, prop: string, value: any): any => {
// Edit modification time
this._data['mtime'] = new Date()
// Apply original changes
return Reflect.set(target, prop, value)
},
deleteProperty: (target: Attribute, prop: string) => {
// Edit modification time
this._data['mtime'] = new Date()
// Apply original changes
return Reflect.deleteProperty(target, prop)
},
} as ProxyHandler<any>

// Proxy the attributes to update the mtime on change
this._attributes = new Proxy(data.attributes || {} as any, handler)
delete this._data.attributes

if (davService) {
Expand Down Expand Up @@ -87,6 +105,20 @@ export abstract class Node {
return this._data.mime
}

/**
* Get the file modification time
*/
get mtime(): Date|undefined {
skjnldsv marked this conversation as resolved.
Show resolved Hide resolved
return this._data.mtime
}

/**
* Get the file creation time
*/
get crtime(): Date|undefined {
skjnldsv marked this conversation as resolved.
Show resolved Hide resolved
return this._data.crtime
}

/**
* Get the file size
*/
Expand Down Expand Up @@ -171,6 +203,7 @@ export abstract class Node {
*/
move(destination: string) {
this._data.source = destination
this._data.mtime = new Date()
}

/**
Expand Down