mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #2495 from overleaf/em-strict-type
Merge getType and getStrictType GitOrigin-RevId: ef0967457f21187be37e96697f9f4262a275d26d
This commit is contained in:
parent
a7db312404
commit
99d0ebe8b1
8 changed files with 451 additions and 502 deletions
|
@ -80,10 +80,18 @@ module.exports = UpdateMerger = {
|
|||
return callback(err)
|
||||
}
|
||||
// determine whether the update should create a doc or binary file
|
||||
FileTypeManager.getStrictType(path, fsPath, function(err, isBinary) {
|
||||
FileTypeManager.getType(path, fsPath, function(
|
||||
err,
|
||||
{ binary, encoding }
|
||||
) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
// If we receive a non-utf8 encoding, we won't be able to keep things in
|
||||
// sync, so we'll treat non-utf8 files as binary
|
||||
const isBinary = binary || encoding !== 'utf-8'
|
||||
|
||||
// Existing | Update | Action
|
||||
// ---------|-----------|-------
|
||||
// file | isBinary | existing-file
|
||||
|
@ -98,6 +106,7 @@ module.exports = UpdateMerger = {
|
|||
if (existingFileType === 'file') {
|
||||
return callback(null, 'existing-file')
|
||||
}
|
||||
|
||||
// if there is an existing doc, keep it as a doc except when the
|
||||
// incoming update is binary. In that case delete the doc and replace
|
||||
// it with a new file.
|
||||
|
|
|
@ -28,7 +28,7 @@ module.exports = FileSystemImportManager = {
|
|||
folder_id,
|
||||
name,
|
||||
path,
|
||||
charset,
|
||||
encoding,
|
||||
replace,
|
||||
callback
|
||||
) {
|
||||
|
@ -46,7 +46,7 @@ module.exports = FileSystemImportManager = {
|
|||
)
|
||||
return callback(new Error('path is symlink'))
|
||||
}
|
||||
return fs.readFile(path, charset, function(error, content) {
|
||||
return fs.readFile(path, encoding, function(error, content) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
|
@ -252,11 +252,11 @@ module.exports = FileSystemImportManager = {
|
|||
return FileTypeManager.getType(
|
||||
name,
|
||||
path,
|
||||
(error, isBinary, charset) => {
|
||||
(error, { binary, encoding }) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
if (isBinary) {
|
||||
if (binary) {
|
||||
return FileSystemImportManager.addFile(
|
||||
user_id,
|
||||
project_id,
|
||||
|
@ -278,7 +278,7 @@ module.exports = FileSystemImportManager = {
|
|||
folder_id,
|
||||
name,
|
||||
path,
|
||||
charset,
|
||||
encoding,
|
||||
replace,
|
||||
function(err, entity) {
|
||||
if (entity != null) {
|
||||
|
|
|
@ -1,61 +1,48 @@
|
|||
/* eslint-disable
|
||||
handle-callback-err,
|
||||
max-len,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let FileTypeManager
|
||||
const fs = require('fs')
|
||||
const Path = require('path')
|
||||
const isUtf8 = require('is-utf8')
|
||||
const isUtf8 = require('utf-8-validate')
|
||||
|
||||
module.exports = FileTypeManager = {
|
||||
const FileTypeManager = {
|
||||
TEXT_EXTENSIONS: [
|
||||
'tex',
|
||||
'latex',
|
||||
'sty',
|
||||
'cls',
|
||||
'bst',
|
||||
'bib',
|
||||
'bibtex',
|
||||
'txt',
|
||||
'tikz',
|
||||
'rtex',
|
||||
'md',
|
||||
'asy',
|
||||
'latexmkrc',
|
||||
'lbx',
|
||||
'bbx',
|
||||
'cbx',
|
||||
'm'
|
||||
'.tex',
|
||||
'.latex',
|
||||
'.sty',
|
||||
'.cls',
|
||||
'.bst',
|
||||
'.bib',
|
||||
'.bibtex',
|
||||
'.txt',
|
||||
'.tikz',
|
||||
'.rtex',
|
||||
'.md',
|
||||
'.asy',
|
||||
'.latexmkrc',
|
||||
'.lbx',
|
||||
'.bbx',
|
||||
'.cbx',
|
||||
'.m'
|
||||
],
|
||||
|
||||
IGNORE_EXTENSIONS: [
|
||||
'dvi',
|
||||
'aux',
|
||||
'log',
|
||||
'toc',
|
||||
'out',
|
||||
'pdfsync',
|
||||
'.dvi',
|
||||
'.aux',
|
||||
'.log',
|
||||
'.toc',
|
||||
'.out',
|
||||
'.pdfsync',
|
||||
// Index and glossary files
|
||||
'nlo',
|
||||
'ind',
|
||||
'glo',
|
||||
'gls',
|
||||
'glg',
|
||||
'.nlo',
|
||||
'.ind',
|
||||
'.glo',
|
||||
'.gls',
|
||||
'.glg',
|
||||
// Bibtex
|
||||
'bbl',
|
||||
'blg',
|
||||
'.bbl',
|
||||
'.blg',
|
||||
// Misc/bad
|
||||
'doc',
|
||||
'docx',
|
||||
'gz'
|
||||
'.doc',
|
||||
'.docx',
|
||||
'.gz'
|
||||
],
|
||||
|
||||
IGNORE_FILENAMES: ['__MACOSX', '.git', '.gitignore'],
|
||||
|
@ -63,113 +50,82 @@ module.exports = FileTypeManager = {
|
|||
MAX_TEXT_FILE_SIZE: 1 * 1024 * 1024, // 1 MB
|
||||
|
||||
isDirectory(path, callback) {
|
||||
if (callback == null) {
|
||||
callback = function(error, result) {}
|
||||
}
|
||||
return fs.stat(path, function(error, stats) {
|
||||
fs.stat(path, (error, stats) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
return callback(null, stats != null ? stats.isDirectory() : undefined)
|
||||
callback(null, stats.isDirectory())
|
||||
})
|
||||
},
|
||||
|
||||
// returns charset as understood by fs.readFile,
|
||||
getType(name, fsPath, callback) {
|
||||
if (callback == null) {
|
||||
callback = function(error, isBinary, charset, bytes) {}
|
||||
}
|
||||
const parts = name.split('.')
|
||||
const extension = parts.slice(-1)[0].toLowerCase()
|
||||
const isText =
|
||||
(FileTypeManager.TEXT_EXTENSIONS.indexOf(extension) > -1 &&
|
||||
parts.length > 1) ||
|
||||
parts[0] === 'latexmkrc'
|
||||
|
||||
if (!isText) {
|
||||
return callback(null, true)
|
||||
if (!_isTextFilename(name)) {
|
||||
return callback(null, { binary: true })
|
||||
}
|
||||
|
||||
return fs.stat(fsPath, function(error, stat) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
if (stat.size > FileTypeManager.MAX_TEXT_FILE_SIZE) {
|
||||
return callback(null, true) // Treat large text file as binary
|
||||
}
|
||||
|
||||
return fs.readFile(fsPath, function(err, bytes) {
|
||||
fs.stat(fsPath, (err, stat) => {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
if (isUtf8(bytes)) {
|
||||
return callback(null, false, 'utf-8', bytes)
|
||||
}
|
||||
// check for little-endian unicode bom (nodejs does not support big-endian)
|
||||
if (bytes[0] === 0xff && bytes[1] === 0xfe) {
|
||||
return callback(null, false, 'utf-16le')
|
||||
if (stat.size > FileTypeManager.MAX_TEXT_FILE_SIZE) {
|
||||
return callback(null, { binary: true }) // Treat large text file as binary
|
||||
}
|
||||
|
||||
return callback(null, false, 'latin1')
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// For compatibility with the history service, only accept valid utf8 with no
|
||||
// nulls or non-BMP characters as text, eveything else is binary.
|
||||
getStrictType(name, fsPath, callback) {
|
||||
FileTypeManager.getType(name, fsPath, function(
|
||||
err,
|
||||
isBinary,
|
||||
charset,
|
||||
bytes
|
||||
) {
|
||||
if (err) {
|
||||
fs.readFile(fsPath, (err, bytes) => {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
if (isBinary || charset !== 'utf-8' || !bytes) {
|
||||
return callback(null, true)
|
||||
const encoding = _detectEncoding(bytes)
|
||||
const text = bytes.toString(encoding)
|
||||
// For compatibility with the history service, only accept valid utf8 with no
|
||||
// nulls or non-BMP characters as text, eveything else is binary.
|
||||
if (text.includes('\x00')) {
|
||||
return callback(null, { binary: true })
|
||||
}
|
||||
let data = bytes.toString()
|
||||
if (data.indexOf('\x00') !== -1) {
|
||||
return callback(null, true)
|
||||
}
|
||||
if (/[\uD800-\uDFFF]/.test(data)) {
|
||||
if (/[\uD800-\uDFFF]/.test(text)) {
|
||||
// non-BMP characters (high and low surrogate characters)
|
||||
return callback(null, true)
|
||||
return callback(null, { binary: true })
|
||||
}
|
||||
return callback(null, false)
|
||||
callback(null, { binary: false, encoding })
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
getExtension(fileName) {
|
||||
const nameSplit = fileName.split('.')
|
||||
if (nameSplit.length < 2) {
|
||||
return undefined
|
||||
}
|
||||
return nameSplit.pop()
|
||||
},
|
||||
|
||||
shouldIgnore(path, callback) {
|
||||
if (callback == null) {
|
||||
callback = function(error, result) {}
|
||||
}
|
||||
const name = Path.basename(path)
|
||||
let extension = this.getExtension(name)
|
||||
if (extension != null) {
|
||||
extension = extension.toLowerCase()
|
||||
}
|
||||
let extension = Path.extname(name).toLowerCase()
|
||||
let ignore = false
|
||||
if (name[0] === '.' && extension !== 'latexmkrc') {
|
||||
if (name.startsWith('.') && name !== '.latexmkrc') {
|
||||
ignore = true
|
||||
}
|
||||
if (this.IGNORE_EXTENSIONS.indexOf(extension) !== -1) {
|
||||
if (FileTypeManager.IGNORE_EXTENSIONS.includes(extension)) {
|
||||
ignore = true
|
||||
}
|
||||
if (this.IGNORE_FILENAMES.indexOf(name) !== -1) {
|
||||
if (FileTypeManager.IGNORE_FILENAMES.includes(name)) {
|
||||
ignore = true
|
||||
}
|
||||
return callback(null, ignore)
|
||||
callback(null, ignore)
|
||||
}
|
||||
}
|
||||
|
||||
function _isTextFilename(filename) {
|
||||
const extension = Path.extname(filename).toLowerCase()
|
||||
return (
|
||||
FileTypeManager.TEXT_EXTENSIONS.includes(extension) ||
|
||||
filename === 'latexmkrc'
|
||||
)
|
||||
}
|
||||
|
||||
function _detectEncoding(bytes) {
|
||||
if (isUtf8(bytes)) {
|
||||
return 'utf-8'
|
||||
}
|
||||
// check for little-endian unicode bom (nodejs does not support big-endian)
|
||||
if (bytes[0] === 0xff && bytes[1] === 0xfe) {
|
||||
return 'utf-16le'
|
||||
}
|
||||
return 'latin1'
|
||||
}
|
||||
|
||||
module.exports = FileTypeManager
|
||||
|
|
78
services/web/package-lock.json
generated
78
services/web/package-lock.json
generated
|
@ -5902,7 +5902,7 @@
|
|||
"ctype": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz",
|
||||
"integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=",
|
||||
"integrity": "sha512-T6CEkoSV4q50zW3TlTHMbzy1E5+zlnNcY+yb7tWVYlTwPhx9LpnfAkd4wecpWknDyptp4k97LUZeInlf6jdzBg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
|
@ -10370,7 +10370,7 @@
|
|||
"i18next": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-1.10.6.tgz",
|
||||
"integrity": "sha1-/d2LSRUCxIlnpiljvHIv+JfN3qA=",
|
||||
"integrity": "sha512-dWqoNEjzG+Dxgm0gxJlSsNu/PUn6d8wXmCEsY1mJgwCsTE/5hac29krq92IPpj59dKypD59bMJyNAd0VYvvIXw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cookies": ">= 0.2.2",
|
||||
|
@ -10381,7 +10381,7 @@
|
|||
"json5": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-0.2.0.tgz",
|
||||
"integrity": "sha1-ttcDXHDEVw+IPH7cdZ3jrgPbM0M=",
|
||||
"integrity": "sha512-jzu3hxGhztAzldgKTbsW240mtPIgR6foddu9HqQgpv0ML2RcjE0mjyLro0XE92YAQYpTpcByY80vVzlKOM64xA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
@ -10389,7 +10389,7 @@
|
|||
"i18next-client": {
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/i18next-client/-/i18next-client-1.10.3.tgz",
|
||||
"integrity": "sha1-dtA1NVftkNHnqHdU1QBNP3gB/ek=",
|
||||
"integrity": "sha512-f0Hmy8ES4B5+PolvMbu9TyATA8Sc9klHW9QwDGNTwqoN8A090BECCz3/e6yePF/HNDvAQgwTxpGQgA3+qfWTeA==",
|
||||
"dev": true
|
||||
},
|
||||
"iconv-lite": {
|
||||
|
@ -11141,11 +11141,6 @@
|
|||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
|
||||
},
|
||||
"is-utf8": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
|
||||
"integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q=="
|
||||
},
|
||||
"is-windows": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
|
||||
|
@ -12452,7 +12447,7 @@
|
|||
"lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.defaults": {
|
||||
|
@ -12463,7 +12458,7 @@
|
|||
"lodash.escaperegexp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
||||
"integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=",
|
||||
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.flatten": {
|
||||
|
@ -14593,6 +14588,11 @@
|
|||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz",
|
||||
"integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw=="
|
||||
},
|
||||
"node-gyp-build": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz",
|
||||
"integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w=="
|
||||
},
|
||||
"node-html-encoder": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/node-html-encoder/-/node-html-encoder-0.0.2.tgz",
|
||||
|
@ -15665,7 +15665,7 @@
|
|||
"onesky": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/onesky/-/onesky-0.1.6.tgz",
|
||||
"integrity": "sha1-fw1oy4DN/51ETHzVURZdgow0zaw=",
|
||||
"integrity": "sha512-usE1IfmUWtaqCUkjcIid/L1XDLZkdEgWmS5O/kqKDBD68t99Y8+BX5cck9QKiKYRtrgDZLel4H5BvW6myUbSHg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"request": "~2.40.0",
|
||||
|
@ -15675,35 +15675,35 @@
|
|||
"asn1": {
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz",
|
||||
"integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=",
|
||||
"integrity": "sha512-Fh9zh3G2mZ8qM/kwsiKwL2U2FmXxVsboP4x1mXjnhKHv3SmzaBZoYvxEQJz/YS2gnCgd8xlAVWcZnQyC9qZBsA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"assert-plus": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz",
|
||||
"integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=",
|
||||
"integrity": "sha512-brU24g7ryhRwGCI2y+1dGQmQXiZF7TtIj583S96y0jjdajIe6wn8BuXyELYhvD22dtIxDQVFk04YTJwwdwOYJw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"async": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
|
||||
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=",
|
||||
"integrity": "sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"aws-sign2": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz",
|
||||
"integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=",
|
||||
"integrity": "sha512-oqUX0DM5j7aPWPCnpWebiyNIj2wiNI87ZxnOMoGv0aE4TGlBy2N+5iWc6dQ/NOKZaBD2W6PVz8jtOGkWzSC5EA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"boom": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz",
|
||||
"integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=",
|
||||
"integrity": "sha512-OvfN8y1oAxxphzkl2SnCS+ztV/uVKTATtgLjWYg/7KwcNyf3rzpHxNQJZCKtsZd4+MteKczhWbSjtEX4bGgU9g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hoek": "0.9.x"
|
||||
|
@ -15712,7 +15712,7 @@
|
|||
"combined-stream": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
|
||||
"integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=",
|
||||
"integrity": "sha512-qfexlmLp9MyrkajQVyjEDb0Vj+KhRgR/rxLiVhaihlT+ZkX0lReqtH6Ack40CvMDERR4b5eFp3CreskpBs1Pig==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -15722,7 +15722,7 @@
|
|||
"cryptiles": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz",
|
||||
"integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=",
|
||||
"integrity": "sha512-gvWSbgqP+569DdslUiCelxIv3IYK5Lgmq1UrRnk+s1WxQOQ16j3GPDcjdtgL5Au65DU/xQi6q3xPtf5Kta+3IQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -15732,20 +15732,20 @@
|
|||
"delayed-stream": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz",
|
||||
"integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=",
|
||||
"integrity": "sha512-v+7uBd1pqe5YtgPacIIbZ8HuHeLFVNe4mUEyFDXL6KiqzEykjbw+5mXZXpGFgNVasdL4jWKgaKIXrEHiynN1LA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz",
|
||||
"integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=",
|
||||
"integrity": "sha512-PDG5Ef0Dob/JsZUxUltJOhm/Y9mlteAE+46y3M9RBz/Rd3QVENJ75aGRhN56yekTUboaBIkd8KVWX2NjF6+91A==",
|
||||
"dev": true
|
||||
},
|
||||
"form-data": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz",
|
||||
"integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=",
|
||||
"integrity": "sha512-x8eE+nzFtAMA0YYlSxf/Qhq6vP1f8wSoZ7Aw1GuctBcmudCNuTUmmx45TfEplyb6cjsZO/jvh6+1VpZn24ez+w==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -15757,7 +15757,7 @@
|
|||
"hawk": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz",
|
||||
"integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=",
|
||||
"integrity": "sha512-am8sVA2bCJIw8fuuVcKvmmNnGFUGW8spTkVtj2fXTEZVkfN42bwFZFtDem57eFi+NSxurJB8EQ7Jd3uCHLn8Vw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -15770,13 +15770,13 @@
|
|||
"hoek": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz",
|
||||
"integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=",
|
||||
"integrity": "sha512-ZZ6eGyzGjyMTmpSPYVECXy9uNfqBR7x5CavhUaLOeD6W0vWK1mp/b7O3f86XE0Mtfo9rZ6Bh3fnuw9Xr8MF9zA==",
|
||||
"dev": true
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz",
|
||||
"integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=",
|
||||
"integrity": "sha512-coK8uR5rq2IMj+Hen+sKPA5ldgbCc1/spPdKCL1Fw6h+D0s/2LzMcRK0Cqufs1h0ryx/niwBHGFu8HC3hwU+lA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -15788,39 +15788,39 @@
|
|||
"mime": {
|
||||
"version": "1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz",
|
||||
"integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=",
|
||||
"integrity": "sha512-Ysa2F/nqTNGHhhm9MV8ure4+Hc+Y8AWiqUdHxsO7xu8zc92ND9f3kpALHjaP026Ft17UfxrMt95c50PLUeynBw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz",
|
||||
"integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=",
|
||||
"integrity": "sha512-echfutj/t5SoTL4WZpqjA1DCud1XO0WQF3/GJ48YBmc4ZMhCK77QA6Z/w6VTQERLKuJ4drze3kw2TUT8xZXVNw==",
|
||||
"dev": true
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
|
||||
"integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=",
|
||||
"integrity": "sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA==",
|
||||
"dev": true
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz",
|
||||
"integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=",
|
||||
"integrity": "sha512-Tr31Sh5FnK9YKm7xTUPyDMsNOvMqkVDND0zvK/Wgj7/H9q8mpye0qG2nVzrnsvLhcsX5DtqXD0la0ks6rkPCGQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"qs": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-1.0.2.tgz",
|
||||
"integrity": "sha1-UKk+K1r2aRwxvOpdrnjubqGQN2g=",
|
||||
"integrity": "sha512-tHuOP9TN/1VmDM/ylApGK1QF3PSIP8I6bHDEfoKNQeViREQ/sfu1bAUrA1hoDun8p8Tpm7jcsz47g+3PiGoYdg==",
|
||||
"dev": true
|
||||
},
|
||||
"request": {
|
||||
"version": "2.40.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.40.0.tgz",
|
||||
"integrity": "sha1-TdZw9pbx5uhC5mtLXoOTAaub62c=",
|
||||
"integrity": "sha512-waNoGB4Z7bPn+lgqPk7l7hhze4Vd68jKccnwLeS7vr9GMxz0iWQbYTbBNWzfIk87Urx7V44pu29qjF/omej+Fw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"aws-sign2": "~0.5.0",
|
||||
|
@ -15841,7 +15841,7 @@
|
|||
"sntp": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz",
|
||||
"integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=",
|
||||
"integrity": "sha512-bDLrKa/ywz65gCl+LmOiIhteP1bhEsAAzhfMedPoiHP3dyYnAevlaJshdqb9Yu0sRifyP/fRqSt8t+5qGIWlGQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -15851,7 +15851,7 @@
|
|||
"tunnel-agent": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
|
||||
"integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
|
||||
"integrity": "sha512-e0IoVDWx8SDHc/hwFTqJDQ7CCDTEeGhmcT9jkWJjoGQSpgBz20nAMr80E3Tpk7PatJ1b37DQDgJR3CNSzcMOZQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
|
@ -20304,7 +20304,7 @@
|
|||
"srcset": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz",
|
||||
"integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=",
|
||||
"integrity": "sha512-UH8e80l36aWnhACzjdtLspd4TAWldXJMa45NuOkTTU+stwekswObdqM63TtQixN4PPd/vO/kxLa6RD+tUPeFMg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-uniq": "^1.0.2",
|
||||
|
@ -21369,7 +21369,7 @@
|
|||
}
|
||||
},
|
||||
"translations-sharelatex": {
|
||||
"version": "git+https://github.com/sharelatex/translations-sharelatex.git#b5fc8bb408a9d04fc040b8ebc132ce093930e89d",
|
||||
"version": "git+https://github.com/sharelatex/translations-sharelatex.git#beea1036cdf3adf41cd41e73fcfd6a5a70f83763",
|
||||
"from": "git+https://github.com/sharelatex/translations-sharelatex.git#master",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -21929,6 +21929,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.2.tgz",
|
||||
"integrity": "sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==",
|
||||
"requires": {
|
||||
"node-gyp-build": "~3.7.0"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
|
|
@ -64,7 +64,6 @@
|
|||
"handlebars": "^4.0.11",
|
||||
"helmet": "^3.8.1",
|
||||
"http-proxy": "^1.8.1",
|
||||
"is-utf8": "^0.2.1",
|
||||
"joi-mongodb-objectid": "^0.1.0",
|
||||
"jquery": "^2.2.4",
|
||||
"json2csv": "^4.3.3",
|
||||
|
@ -114,6 +113,7 @@
|
|||
"temp": "^0.8.3",
|
||||
"underscore": "1.6.0",
|
||||
"url-parse": "^1.4.7",
|
||||
"utf-8-validate": "^5.0.2",
|
||||
"uuid": "^3.0.1",
|
||||
"valid-url": "^1.0.9",
|
||||
"xml2js": "^0.4.22",
|
||||
|
|
|
@ -1,19 +1,5 @@
|
|||
/* eslint-disable
|
||||
max-len,
|
||||
no-return-assign,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const Stream = require('stream')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
require('chai').should()
|
||||
const modulePath = require('path').join(
|
||||
__dirname,
|
||||
'../../../../app/src/Features/ThirdPartyDataStore/UpdateMerger.js'
|
||||
|
@ -60,15 +46,17 @@ describe('UpdateMerger :', function() {
|
|||
this.source = 'dropbox'
|
||||
this.updateRequest = new BufferedStream()
|
||||
this.FileWriter.writeStreamToDisk = sinon.stub().yields(null, this.fsPath)
|
||||
return (this.callback = sinon.stub())
|
||||
this.callback = sinon.stub()
|
||||
})
|
||||
|
||||
describe('mergeUpdate', function() {
|
||||
describe('doc updates for a new doc', function() {
|
||||
beforeEach(function() {
|
||||
this.FileTypeManager.getStrictType = sinon.stub().yields(null, false)
|
||||
this.FileTypeManager.getType = sinon
|
||||
.stub()
|
||||
.yields(null, { binary: false, encoding: 'utf-8' })
|
||||
this.updateMerger.p.processDoc = sinon.stub().yields()
|
||||
return this.updateMerger.mergeUpdate(
|
||||
this.updateMerger.mergeUpdate(
|
||||
this.user_id,
|
||||
this.project_id,
|
||||
this.docPath,
|
||||
|
@ -79,11 +67,11 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('should look at the file contents', function() {
|
||||
return this.FileTypeManager.getStrictType.called.should.equal(true)
|
||||
this.FileTypeManager.getType.called.should.equal(true)
|
||||
})
|
||||
|
||||
it('should process update as doc', function() {
|
||||
return this.updateMerger.p.processDoc
|
||||
this.updateMerger.p.processDoc
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
|
@ -95,15 +83,17 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('removes the temp file from disk', function() {
|
||||
return this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('file updates for a new file ', function() {
|
||||
beforeEach(function() {
|
||||
this.FileTypeManager.getStrictType = sinon.stub().yields(null, true)
|
||||
this.FileTypeManager.getType = sinon
|
||||
.stub()
|
||||
.yields(null, { binary: true })
|
||||
this.updateMerger.p.processFile = sinon.stub().yields()
|
||||
return this.updateMerger.mergeUpdate(
|
||||
this.updateMerger.mergeUpdate(
|
||||
this.user_id,
|
||||
this.project_id,
|
||||
this.filePath,
|
||||
|
@ -114,11 +104,11 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('should look at the file contents', function() {
|
||||
return this.FileTypeManager.getStrictType.called.should.equal(true)
|
||||
this.FileTypeManager.getType.called.should.equal(true)
|
||||
})
|
||||
|
||||
it('should process update as file', function() {
|
||||
return this.updateMerger.p.processFile
|
||||
this.updateMerger.p.processFile
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.fsPath,
|
||||
|
@ -130,15 +120,17 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('removes the temp file from disk', function() {
|
||||
return this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('doc updates for an existing doc', function() {
|
||||
beforeEach(function() {
|
||||
this.FileTypeManager.getStrictType = sinon.stub().yields(null, false)
|
||||
this.FileTypeManager.getType = sinon
|
||||
.stub()
|
||||
.yields(null, { binary: false, encoding: 'utf-8' })
|
||||
this.updateMerger.p.processDoc = sinon.stub().yields()
|
||||
return this.updateMerger.mergeUpdate(
|
||||
this.updateMerger.mergeUpdate(
|
||||
this.user_id,
|
||||
this.project_id,
|
||||
this.existingDocPath,
|
||||
|
@ -149,11 +141,11 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('should look at the file contents', function() {
|
||||
return this.FileTypeManager.getStrictType.called.should.equal(true)
|
||||
this.FileTypeManager.getType.called.should.equal(true)
|
||||
})
|
||||
|
||||
it('should process update as doc', function() {
|
||||
return this.updateMerger.p.processDoc
|
||||
this.updateMerger.p.processDoc
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
|
@ -165,15 +157,17 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('removes the temp file from disk', function() {
|
||||
return this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('file updates for an existing file', function() {
|
||||
beforeEach(function() {
|
||||
this.FileTypeManager.getStrictType = sinon.stub().yields(null, true)
|
||||
this.FileTypeManager.getType = sinon
|
||||
.stub()
|
||||
.yields(null, { binary: true })
|
||||
this.updateMerger.p.processFile = sinon.stub().yields()
|
||||
return this.updateMerger.mergeUpdate(
|
||||
this.updateMerger.mergeUpdate(
|
||||
this.user_id,
|
||||
this.project_id,
|
||||
this.existingFilePath,
|
||||
|
@ -184,11 +178,11 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('should look at the file contents', function() {
|
||||
return this.FileTypeManager.getStrictType.called.should.equal(true)
|
||||
this.FileTypeManager.getType.called.should.equal(true)
|
||||
})
|
||||
|
||||
it('should process update as file', function() {
|
||||
return this.updateMerger.p.processFile
|
||||
this.updateMerger.p.processFile
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.fsPath,
|
||||
|
@ -200,19 +194,17 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('removes the temp file from disk', function() {
|
||||
return this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('file updates for an existing doc', function() {
|
||||
beforeEach(function() {
|
||||
this.FileTypeManager.getStrictType = sinon
|
||||
.stub()
|
||||
.yields(null, true, 'delete-existing-doc')
|
||||
this.FileTypeManager.getType = sinon.stub().yields(null, { binary: true })
|
||||
this.updateMerger.deleteUpdate = sinon.stub().yields()
|
||||
this.updateMerger.p.processFile = sinon.stub().yields()
|
||||
return this.updateMerger.mergeUpdate(
|
||||
this.updateMerger.mergeUpdate(
|
||||
this.user_id,
|
||||
this.project_id,
|
||||
this.existingDocPath,
|
||||
|
@ -223,7 +215,7 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('should look at the file contents', function() {
|
||||
return this.FileTypeManager.getStrictType.called.should.equal(true)
|
||||
this.FileTypeManager.getType.called.should.equal(true)
|
||||
})
|
||||
|
||||
it('should delete the existing doc', function() {
|
||||
|
@ -238,7 +230,7 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('should process update as file', function() {
|
||||
return this.updateMerger.p.processFile
|
||||
this.updateMerger.p.processFile
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.fsPath,
|
||||
|
@ -250,16 +242,16 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('removes the temp file from disk', function() {
|
||||
return this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('doc updates for an existing file', function() {
|
||||
beforeEach(function() {
|
||||
this.FileTypeManager.getStrictType = sinon.stub().yields(null, true)
|
||||
this.FileTypeManager.getType = sinon.stub().yields(null, { binary: true })
|
||||
this.updateMerger.deleteUpdate = sinon.stub().yields()
|
||||
this.updateMerger.p.processFile = sinon.stub().yields()
|
||||
return this.updateMerger.mergeUpdate(
|
||||
this.updateMerger.mergeUpdate(
|
||||
this.user_id,
|
||||
this.project_id,
|
||||
this.existingFilePath,
|
||||
|
@ -270,7 +262,7 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('should look at the file contents', function() {
|
||||
return this.FileTypeManager.getStrictType.called.should.equal(true)
|
||||
this.FileTypeManager.getType.called.should.equal(true)
|
||||
})
|
||||
|
||||
it('should not delete the existing file', function() {
|
||||
|
@ -278,7 +270,7 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('should process update as file', function() {
|
||||
return this.updateMerger.p.processFile
|
||||
this.updateMerger.p.processFile
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.fsPath,
|
||||
|
@ -290,14 +282,14 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('removes the temp file from disk', function() {
|
||||
return this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
this.fs.unlink.calledWith(this.fsPath).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteUpdate', function() {
|
||||
beforeEach(function() {
|
||||
this.EditorController.deleteEntityWithPath = sinon.stub().yields()
|
||||
return this.updateMerger.deleteUpdate(
|
||||
this.updateMerger.deleteUpdate(
|
||||
this.user_id,
|
||||
this.project_id,
|
||||
this.docPath,
|
||||
|
@ -307,7 +299,7 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('should delete the entity in the editor controller', function() {
|
||||
return this.EditorController.deleteEntityWithPath
|
||||
this.EditorController.deleteEntityWithPath
|
||||
.calledWith(this.project_id, this.docPath, this.source, this.user_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
@ -323,7 +315,7 @@ describe('UpdateMerger :', function() {
|
|||
.yields(null, this.docLines)
|
||||
this.EditorController.upsertDocWithPath = sinon.stub().yields()
|
||||
|
||||
return this.updateMerger.p.processDoc(
|
||||
this.updateMerger.p.processDoc(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
this.fsPath,
|
||||
|
@ -334,13 +326,13 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('reads the temp file from disk', function() {
|
||||
return this.updateMerger.p.readFileIntoTextArray
|
||||
this.updateMerger.p.readFileIntoTextArray
|
||||
.calledWith(this.fsPath)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should upsert the doc in the editor controller', function() {
|
||||
return this.EditorController.upsertDocWithPath
|
||||
this.EditorController.upsertDocWithPath
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.docPath,
|
||||
|
@ -355,7 +347,7 @@ describe('UpdateMerger :', function() {
|
|||
describe('processFile', function() {
|
||||
beforeEach(function() {
|
||||
this.EditorController.upsertFileWithPath = sinon.stub().yields()
|
||||
return this.updateMerger.p.processFile(
|
||||
this.updateMerger.p.processFile(
|
||||
this.project_id,
|
||||
this.fsPath,
|
||||
this.filePath,
|
||||
|
@ -366,7 +358,7 @@ describe('UpdateMerger :', function() {
|
|||
})
|
||||
|
||||
it('should upsert the file in the editor controller', function() {
|
||||
return this.EditorController.upsertFileWithPath
|
||||
this.EditorController.upsertFileWithPath
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.filePath,
|
||||
|
|
|
@ -482,7 +482,9 @@ describe('FileSystemImportManager', function() {
|
|||
this.FileTypeManager.isDirectory = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, false)
|
||||
this.FileTypeManager.getType = sinon.stub().callsArgWith(2, null, true)
|
||||
this.FileTypeManager.getType = sinon
|
||||
.stub()
|
||||
.yields(null, { binary: true })
|
||||
this.FileSystemImportManager._isSafeOnFileSystem = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, true)
|
||||
|
@ -519,7 +521,7 @@ describe('FileSystemImportManager', function() {
|
|||
.callsArgWith(1, null, false)
|
||||
this.FileTypeManager.getType = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, false, 'latin1')
|
||||
.yields(null, { binary: false, encoding: 'latin1' })
|
||||
this.FileSystemImportManager.addDoc = sinon.stub().callsArg(7)
|
||||
this.FileSystemImportManager._isSafeOnFileSystem = sinon
|
||||
.stub()
|
||||
|
|
|
@ -1,371 +1,353 @@
|
|||
/* eslint-disable
|
||||
handle-callback-err,
|
||||
max-len,
|
||||
no-return-assign,
|
||||
no-unused-vars,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const sinon = require('sinon')
|
||||
const expect = require('chai').expect
|
||||
const modulePath = '../../../../app/src/Features/Uploads/FileTypeManager.js'
|
||||
const { expect } = require('chai')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const isUtf8 = require('is-utf8')
|
||||
const isUtf8 = require('utf-8-validate')
|
||||
const modulePath = '../../../../app/src/Features/Uploads/FileTypeManager.js'
|
||||
|
||||
describe('FileTypeManager', function() {
|
||||
beforeEach(function() {
|
||||
this.isUtf8 = sinon.spy(isUtf8)
|
||||
this.fs = {}
|
||||
this.path = '/path/to/test'
|
||||
this.stats = {
|
||||
isDirectory: sinon.stub().returns(false),
|
||||
size: 100
|
||||
}
|
||||
const fileContents = 'Ich bin eine kleine Teekanne, kurz und kräftig.'
|
||||
this.fs = {
|
||||
stat: sinon.stub().yields(null, this.stats),
|
||||
readFile: sinon.stub()
|
||||
}
|
||||
this.fs.readFile
|
||||
.withArgs('utf8.tex')
|
||||
.yields(null, Buffer.from(fileContents, 'utf-8'))
|
||||
this.fs.readFile
|
||||
.withArgs('utf16.tex')
|
||||
.yields(null, Buffer.from(`\uFEFF${fileContents}`, 'utf-16le'))
|
||||
this.fs.readFile
|
||||
.withArgs('latin1.tex')
|
||||
.yields(null, Buffer.from(fileContents, 'latin1'))
|
||||
this.fs.readFile
|
||||
.withArgs('latin1-null.tex')
|
||||
.yields(null, Buffer.from(`${fileContents}\x00${fileContents}`, 'utf-8'))
|
||||
this.fs.readFile
|
||||
.withArgs('utf8-null.tex')
|
||||
.yields(null, Buffer.from(`${fileContents}\x00${fileContents}`, 'utf-8'))
|
||||
this.fs.readFile
|
||||
.withArgs('utf8-non-bmp.tex')
|
||||
.yields(null, Buffer.from(`${fileContents}😈`))
|
||||
this.fs.readFile
|
||||
.withArgs('utf8-control-chars.tex')
|
||||
.yields(null, Buffer.from(`${fileContents}\x0c${fileContents}`))
|
||||
this.callback = sinon.stub()
|
||||
this.ced = sinon.stub()
|
||||
this.DocumentHelper = { getEncodingFromTexContent: sinon.stub() }
|
||||
return (this.FileTypeManager = SandboxedModule.require(modulePath, {
|
||||
this.FileTypeManager = SandboxedModule.require(modulePath, {
|
||||
globals: {
|
||||
console: console
|
||||
},
|
||||
requires: {
|
||||
fs: this.fs,
|
||||
'is-utf8': this.isUtf8
|
||||
'utf-8-validate': this.isUtf8
|
||||
}
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('isDirectory', function() {
|
||||
beforeEach(function() {
|
||||
this.stats = {}
|
||||
return (this.fs.stat = sinon.stub().callsArgWith(1, null, this.stats))
|
||||
})
|
||||
|
||||
describe('when it is a directory', function() {
|
||||
beforeEach(function() {
|
||||
this.stats.isDirectory = sinon.stub().returns(true)
|
||||
return this.FileTypeManager.isDirectory(this.path, this.callback)
|
||||
this.stats.isDirectory.returns(true)
|
||||
this.FileTypeManager.isDirectory('/some/path', this.callback)
|
||||
})
|
||||
|
||||
it('should return true', function() {
|
||||
return this.callback.calledWith(null, true).should.equal(true)
|
||||
this.callback.should.have.been.calledWith(null, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when it is not a directory', function() {
|
||||
beforeEach(function() {
|
||||
this.stats.isDirectory = sinon.stub().returns(false)
|
||||
return this.FileTypeManager.isDirectory(this.path, this.callback)
|
||||
this.stats.isDirectory.returns(false)
|
||||
this.FileTypeManager.isDirectory('/some/path', this.callback)
|
||||
})
|
||||
|
||||
it('should return false', function() {
|
||||
return this.callback.calledWith(null, false).should.equal(true)
|
||||
this.callback.should.have.been.calledWith(null, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getType', function() {
|
||||
beforeEach(function() {
|
||||
this.stat = { size: 100 }
|
||||
this.contents = 'Ich bin eine kleine Teekanne, kurz und kräftig.'
|
||||
this.fs.stat = sinon.stub().callsArgWith(1, null, this.stat)
|
||||
this.fs.readFile = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, Buffer.from(this.contents, 'utf-8'))
|
||||
this.fs.readFile
|
||||
.withArgs('/path/on/disk/utf16.tex')
|
||||
.callsArgWith(
|
||||
1,
|
||||
null,
|
||||
Buffer.from(`\uFEFF${this.contents}`, 'utf-16le')
|
||||
)
|
||||
this.fs.readFile
|
||||
.withArgs('/path/on/disk/latin1.tex')
|
||||
.callsArgWith(1, null, Buffer.from(this.contents, 'latin1'))
|
||||
return (this.encoding = 'ASCII')
|
||||
})
|
||||
|
||||
describe('when the file extension is text', function() {
|
||||
it('should return .tex files as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
const TEXT_FILENAMES = [
|
||||
'file.tex',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return .bib files as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.bib',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return .bibtex files as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.bibtex',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return .cls files as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.cls',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return .sty files as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.sty',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return .bst files as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.bst',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return .latexmkrc file as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'.latexmkrc',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return latexmkrc file as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'latexmkrc',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return lbx file as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.lbx',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return bbx file as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.bbx',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return cbx file as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.cbx',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return m file as not binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.m',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should ignore the case of an extension', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.TEX',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return large text files as binary', function() {
|
||||
this.stat.size = 2 * 1024 * 1024 // 2Mb
|
||||
return this.FileTypeManager.getType(
|
||||
'file.TEX'
|
||||
]
|
||||
TEXT_FILENAMES.forEach(filename => {
|
||||
it(`should classify ${filename} as text`, function(done) {
|
||||
this.FileTypeManager.getType(
|
||||
'file.tex',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(true)
|
||||
'utf8.tex',
|
||||
(err, { binary }) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
binary.should.equal(false)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('should classify large text files as binary', function(done) {
|
||||
this.stats.size = 2 * 1024 * 1024 // 2Mb
|
||||
this.FileTypeManager.getType(
|
||||
'file.tex',
|
||||
'utf8.tex',
|
||||
(err, { binary }) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
binary.should.equal(true)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should return try to determine the encoding of large files', function() {
|
||||
this.stat.size = 2 * 1024 * 1024 // 2Mb
|
||||
return this.FileTypeManager.getType('file.tex', '/path/on/disk', () => {
|
||||
return sinon.assert.notCalled(this.isUtf8)
|
||||
it('should not try to determine the encoding of large files', function(done) {
|
||||
this.stats.size = 2 * 1024 * 1024 // 2Mb
|
||||
this.FileTypeManager.getType('file.tex', 'utf8.tex', err => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
sinon.assert.notCalled(this.isUtf8)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should detect the file as utf8', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
it('should detect the encoding of a utf8 file', function(done) {
|
||||
this.FileTypeManager.getType(
|
||||
'file.tex',
|
||||
'/path/on/disk',
|
||||
(error, binary, encoding) => {
|
||||
'utf8.tex',
|
||||
(err, { binary, encoding }) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
sinon.assert.calledOnce(this.isUtf8)
|
||||
this.isUtf8.returned(true).should.equal(true)
|
||||
return encoding.should.equal('utf-8')
|
||||
encoding.should.equal('utf-8')
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should return 'latin1' for non-unicode encodings", function() {
|
||||
return this.FileTypeManager.getType(
|
||||
it("should return 'latin1' for non-unicode encodings", function(done) {
|
||||
this.FileTypeManager.getType(
|
||||
'file.tex',
|
||||
'/path/on/disk/latin1.tex',
|
||||
(error, binary, encoding) => {
|
||||
'latin1.tex',
|
||||
(err, { binary, encoding }) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
sinon.assert.calledOnce(this.isUtf8)
|
||||
this.isUtf8.returned(false).should.equal(true)
|
||||
return encoding.should.equal('latin1')
|
||||
encoding.should.equal('latin1')
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should detect utf16 with BOM as utf-16', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
it('should classify utf16 with BOM as utf-16', function(done) {
|
||||
this.FileTypeManager.getType(
|
||||
'file.tex',
|
||||
'/path/on/disk/utf16.tex',
|
||||
(error, binary, encoding) => {
|
||||
'utf16.tex',
|
||||
(err, { binary, encoding }) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
sinon.assert.calledOnce(this.isUtf8)
|
||||
this.isUtf8.returned(false).should.equal(true)
|
||||
return encoding.should.equal('utf-16le')
|
||||
encoding.should.equal('utf-16le')
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should classify latin1 files with a null char as binary', function(done) {
|
||||
this.FileTypeManager.getType(
|
||||
'file.tex',
|
||||
'latin1-null.tex',
|
||||
(err, { binary }) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(binary).to.equal(true)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should classify utf8 files with a null char as binary', function(done) {
|
||||
this.FileTypeManager.getType(
|
||||
'file.tex',
|
||||
'utf8-null.tex',
|
||||
(err, { binary }) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(binary).to.equal(true)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should classify utf8 files with non-BMP chars as binary', function(done) {
|
||||
this.FileTypeManager.getType(
|
||||
'file.tex',
|
||||
'utf8-non-bmp.tex',
|
||||
(err, { binary }) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(binary).to.equal(true)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should classify utf8 files with ascii control chars as utf-8', function(done) {
|
||||
this.FileTypeManager.getType(
|
||||
'file.tex',
|
||||
'utf8-control-chars.tex',
|
||||
(err, { binary, encoding }) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(binary).to.equal(false)
|
||||
expect(encoding).to.equal('utf-8')
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the file extension is non-text', function() {
|
||||
it('should return .eps files as binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.eps',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(true)
|
||||
const BINARY_FILENAMES = ['file.eps', 'file.dvi', 'file.png', 'tex']
|
||||
BINARY_FILENAMES.forEach(filename => {
|
||||
it(`should classify ${filename} as binary`, function(done) {
|
||||
this.FileTypeManager.getType(
|
||||
'file.tex',
|
||||
'utf8.tex',
|
||||
(err, { binary }) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
binary.should.equal(false)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should return .dvi files as binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.dvi',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(true)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return .png files as binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'file.png',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(true)
|
||||
)
|
||||
})
|
||||
|
||||
it('should return files without extensions as binary', function() {
|
||||
return this.FileTypeManager.getType(
|
||||
'tex',
|
||||
'/path/on/disk',
|
||||
(error, binary) => binary.should.equal(true)
|
||||
)
|
||||
})
|
||||
|
||||
it('should not try to get the character encoding', function() {
|
||||
return this.FileTypeManager.getType('file.png', '/path/on/disk', () => {
|
||||
return sinon.assert.notCalled(this.isUtf8)
|
||||
it('should not try to get the character encoding', function(done) {
|
||||
this.FileTypeManager.getType('file.png', 'utf8.tex', err => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
sinon.assert.notCalled(this.isUtf8)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('shouldIgnore', function() {
|
||||
beforeEach(function() {
|
||||
this.stats = {}
|
||||
})
|
||||
|
||||
it('should ignore tex auxiliary files', function() {
|
||||
return this.FileTypeManager.shouldIgnore('file.aux', (error, ignore) =>
|
||||
it('should ignore tex auxiliary files', function(done) {
|
||||
this.FileTypeManager.shouldIgnore('file.aux', (err, ignore) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
ignore.should.equal(true)
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should ignore dotfiles', function() {
|
||||
return this.FileTypeManager.shouldIgnore('path/.git', (error, ignore) =>
|
||||
it('should ignore dotfiles', function(done) {
|
||||
this.FileTypeManager.shouldIgnore('path/.git', (err, ignore) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
ignore.should.equal(true)
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not ignore .latexmkrc dotfile', function() {
|
||||
return this.FileTypeManager.shouldIgnore(
|
||||
'path/.latexmkrc',
|
||||
(error, ignore) => ignore.should.equal(false)
|
||||
)
|
||||
})
|
||||
|
||||
it('should ignore __MACOSX', function() {
|
||||
return this.FileTypeManager.shouldIgnore(
|
||||
'path/__MACOSX',
|
||||
(error, ignore) => ignore.should.equal(true)
|
||||
)
|
||||
})
|
||||
|
||||
it('should not ignore .tex files', function() {
|
||||
return this.FileTypeManager.shouldIgnore('file.tex', (error, ignore) =>
|
||||
it('should not ignore .latexmkrc dotfile', function(done) {
|
||||
this.FileTypeManager.shouldIgnore('path/.latexmkrc', (err, ignore) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
ignore.should.equal(false)
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should ignore the case of the extension', function() {
|
||||
return this.FileTypeManager.shouldIgnore('file.AUX', (error, ignore) =>
|
||||
it('should ignore __MACOSX', function(done) {
|
||||
this.FileTypeManager.shouldIgnore('path/__MACOSX', (err, ignore) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
ignore.should.equal(true)
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not ignore files with an ignored extension as full name', function() {
|
||||
this.stats.isDirectory = sinon.stub().returns(false)
|
||||
const fileName = this.FileTypeManager.IGNORE_EXTENSIONS[0]
|
||||
this.FileTypeManager.shouldIgnore(fileName, (error, ignore) =>
|
||||
it('should not ignore .tex files', function(done) {
|
||||
this.FileTypeManager.shouldIgnore('file.tex', (err, ignore) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
ignore.should.equal(false)
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not ignore directories with an ignored extension as full name', function() {
|
||||
this.stats.isDirectory = sinon.stub().returns(true)
|
||||
const fileName = this.FileTypeManager.IGNORE_EXTENSIONS[0]
|
||||
this.FileTypeManager.shouldIgnore(fileName, (error, ignore) =>
|
||||
it('should ignore the case of the extension', function(done) {
|
||||
this.FileTypeManager.shouldIgnore('file.AUX', (err, ignore) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
ignore.should.equal(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not ignore files with an ignored extension as full name', function(done) {
|
||||
this.FileTypeManager.shouldIgnore('dvi', (err, ignore) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
ignore.should.equal(false)
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getExtension', function() {
|
||||
it('should return the extension of a file name', function() {
|
||||
expect(this.FileTypeManager.getExtension('example.doc')).to.equal('doc')
|
||||
})
|
||||
|
||||
it('should return the extension with unmodified upper and lower case characters', function() {
|
||||
expect(this.FileTypeManager.getExtension('example.TeX')).to.equal('TeX')
|
||||
})
|
||||
|
||||
it('should return the extension of a file name with multiple dots in the name', function() {
|
||||
expect(this.FileTypeManager.getExtension('example.test.doc')).to.equal(
|
||||
'doc'
|
||||
)
|
||||
})
|
||||
|
||||
it('should return the rest of the string when the file name starts with dot', function() {
|
||||
expect(this.FileTypeManager.getExtension('.example.doc')).to.equal('doc')
|
||||
})
|
||||
|
||||
it('should return undefined when the file name has no extension', function() {
|
||||
expect(this.FileTypeManager.getExtension('example')).to.equal(undefined)
|
||||
it('should not ignore directories with an ignored extension as full name', function(done) {
|
||||
this.stats.isDirectory.returns(true)
|
||||
this.FileTypeManager.shouldIgnore('dvi', (err, ignore) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
ignore.should.equal(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue