+
@@ -165,6 +166,9 @@ export default {
userCanUpload() {
return this.$store.getters['user/getUserCanUpload']
},
+ userCanDelete() {
+ return this.$store.getters['user/getUserCanDelete']
+ },
userToken() {
return this.$store.getters['user/getToken']
},
@@ -222,71 +226,53 @@ export default {
this.coversFound = []
this.hasSearched = false
}
- this.imageUrl = this.media.coverPath || ''
+ this.imageUrl = ''
this.searchTitle = this.mediaMetadata.title || ''
this.searchAuthor = this.mediaMetadata.authorName || ''
if (this.isPodcast) this.provider = 'itunes'
else this.provider = localStorage.getItem('book-cover-provider') || localStorage.getItem('book-provider') || 'google'
},
removeCover() {
- if (!this.media.coverPath) {
- this.imageUrl = ''
+ if (!this.coverPath) {
return
}
- this.updateCover('')
+ this.isProcessing = true
+ this.$axios
+ .$delete(`/api/items/${this.libraryItemId}/cover`)
+ .then(() => {})
+ .catch((error) => {
+ console.error('Failed to remove cover', error)
+ if (error.response?.data) {
+ this.$toast.error(error.response.data)
+ }
+ })
+ .finally(() => {
+ this.isProcessing = false
+ })
},
submitForm() {
this.updateCover(this.imageUrl)
},
async updateCover(cover) {
- if (cover === this.coverPath) {
- console.warn('Cover has not changed..', cover)
+ if (!cover.startsWith('http:') && !cover.startsWith('https:')) {
+ this.$toast.error('Invalid URL')
return
}
this.isProcessing = true
- var success = false
-
- if (!cover) {
- // Remove cover
- success = await this.$axios
- .$delete(`/api/items/${this.libraryItemId}/cover`)
- .then(() => true)
- .catch((error) => {
- console.error('Failed to remove cover', error)
- if (error.response && error.response.data) {
- this.$toast.error(error.response.data)
- }
- return false
- })
- } else if (cover.startsWith('http:') || cover.startsWith('https:')) {
- // Download cover from url and use
- success = await this.$axios.$post(`/api/items/${this.libraryItemId}/cover`, { url: cover }).catch((error) => {
- console.error('Failed to download cover from url', error)
- if (error.response && error.response.data) {
- this.$toast.error(error.response.data)
- }
- return false
+ this.$axios
+ .$post(`/api/items/${this.libraryItemId}/cover`, { url: cover })
+ .then(() => {
+ this.imageUrl = ''
+ this.$toast.success('Update Successful')
})
- } else {
- // Update local cover url
- const updatePayload = {
- cover
- }
- success = await this.$axios.$patch(`/api/items/${this.libraryItemId}/cover`, updatePayload).catch((error) => {
- console.error('Failed to update', error)
- if (error.response && error.response.data) {
- this.$toast.error(error.response.data)
- }
- return false
+ .catch((error) => {
+ console.error('Failed to update cover', error)
+ this.$toast.error(error.response?.data || 'Failed to update cover')
+ })
+ .finally(() => {
+ this.isProcessing = false
})
- }
- if (success) {
- this.$toast.success('Update Successful')
- } else if (this.media.coverPath) {
- this.imageUrl = this.media.coverPath
- }
- this.isProcessing = false
},
getSearchQuery() {
var searchQuery = `provider=${this.provider}&title=${this.searchTitle}`
@@ -319,7 +305,19 @@ export default {
this.hasSearched = true
},
setCover(coverFile) {
- this.updateCover(coverFile.metadata.path)
+ this.isProcessing = true
+ this.$axios
+ .$patch(`/api/items/${this.libraryItemId}/cover`, { cover: coverFile.metadata.path })
+ .then(() => {
+ this.$toast.success('Update Successful')
+ })
+ .catch((error) => {
+ console.error('Failed to set local cover', error)
+ this.$toast.error(error.response?.data || 'Failed to set cover')
+ })
+ .finally(() => {
+ this.isProcessing = false
+ })
}
}
}
diff --git a/client/strings/de.json b/client/strings/de.json
index ccd42ede..b72df02f 100644
--- a/client/strings/de.json
+++ b/client/strings/de.json
@@ -266,6 +266,7 @@
"LabelHost": "Host",
"LabelHour": "Stunde",
"LabelIcon": "Symbol",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "In die Titelliste aufnehmen",
"LabelIncomplete": "Unvollständig",
"LabelInProgress": "In Bearbeitung",
diff --git a/client/strings/en-us.json b/client/strings/en-us.json
index 37478bf0..9195265e 100644
--- a/client/strings/en-us.json
+++ b/client/strings/en-us.json
@@ -266,6 +266,7 @@
"LabelHost": "Host",
"LabelHour": "Hour",
"LabelIcon": "Icon",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Include in Tracklist",
"LabelIncomplete": "Incomplete",
"LabelInProgress": "In Progress",
diff --git a/client/strings/es.json b/client/strings/es.json
index f7d548ba..f03c6352 100644
--- a/client/strings/es.json
+++ b/client/strings/es.json
@@ -266,6 +266,7 @@
"LabelHost": "Host",
"LabelHour": "Hora",
"LabelIcon": "Icono",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Incluir en Tracklist",
"LabelIncomplete": "Incompleto",
"LabelInProgress": "En Proceso",
diff --git a/client/strings/fr.json b/client/strings/fr.json
index 5d0e4b3a..031462b2 100644
--- a/client/strings/fr.json
+++ b/client/strings/fr.json
@@ -266,6 +266,7 @@
"LabelHost": "Hôte",
"LabelHour": "Heure",
"LabelIcon": "Icone",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Inclure dans la liste des pistes",
"LabelIncomplete": "Incomplet",
"LabelInProgress": "En cours",
diff --git a/client/strings/gu.json b/client/strings/gu.json
index 5018cf4d..0803ccf4 100644
--- a/client/strings/gu.json
+++ b/client/strings/gu.json
@@ -266,6 +266,7 @@
"LabelHost": "Host",
"LabelHour": "Hour",
"LabelIcon": "Icon",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Include in Tracklist",
"LabelIncomplete": "Incomplete",
"LabelInProgress": "In Progress",
diff --git a/client/strings/hi.json b/client/strings/hi.json
index 21ed9893..1eea8495 100644
--- a/client/strings/hi.json
+++ b/client/strings/hi.json
@@ -266,6 +266,7 @@
"LabelHost": "Host",
"LabelHour": "Hour",
"LabelIcon": "Icon",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Include in Tracklist",
"LabelIncomplete": "Incomplete",
"LabelInProgress": "In Progress",
diff --git a/client/strings/hr.json b/client/strings/hr.json
index b0e0db91..47908b18 100644
--- a/client/strings/hr.json
+++ b/client/strings/hr.json
@@ -266,6 +266,7 @@
"LabelHost": "Host",
"LabelHour": "Sat",
"LabelIcon": "Ikona",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Dodaj u Tracklist",
"LabelIncomplete": "Nepotpuno",
"LabelInProgress": "U tijeku",
diff --git a/client/strings/it.json b/client/strings/it.json
index 96a24392..b60a87c1 100644
--- a/client/strings/it.json
+++ b/client/strings/it.json
@@ -266,6 +266,7 @@
"LabelHost": "Host",
"LabelHour": "Ora",
"LabelIcon": "Icona",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Includi nella Tracklist",
"LabelIncomplete": "Incompleta",
"LabelInProgress": "In Corso",
diff --git a/client/strings/lt.json b/client/strings/lt.json
index 4f7bf2ed..31d259e6 100644
--- a/client/strings/lt.json
+++ b/client/strings/lt.json
@@ -266,6 +266,7 @@
"LabelHost": "Serveris",
"LabelHour": "Valanda",
"LabelIcon": "Piktograma",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Įtraukti į takelių sąrašą",
"LabelIncomplete": "Nebaigta",
"LabelInProgress": "Vyksta",
diff --git a/client/strings/nl.json b/client/strings/nl.json
index ac61de96..eb6b35b3 100644
--- a/client/strings/nl.json
+++ b/client/strings/nl.json
@@ -266,6 +266,7 @@
"LabelHost": "Host",
"LabelHour": "Uur",
"LabelIcon": "Icoon",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Includeer in tracklijst",
"LabelIncomplete": "Incompleet",
"LabelInProgress": "Bezig",
diff --git a/client/strings/no.json b/client/strings/no.json
index d1f51aac..f4fe316c 100644
--- a/client/strings/no.json
+++ b/client/strings/no.json
@@ -266,6 +266,7 @@
"LabelHost": "Tjener",
"LabelHour": "Time",
"LabelIcon": "Ikon",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Inkluder i sporliste",
"LabelIncomplete": "Ufullstendig",
"LabelInProgress": "I gang",
diff --git a/client/strings/pl.json b/client/strings/pl.json
index c4e6ae84..a645877b 100644
--- a/client/strings/pl.json
+++ b/client/strings/pl.json
@@ -266,6 +266,7 @@
"LabelHost": "Host",
"LabelHour": "Godzina",
"LabelIcon": "Ikona",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Dołącz do listy odtwarzania",
"LabelIncomplete": "Nieukończone",
"LabelInProgress": "W trakcie",
diff --git a/client/strings/ru.json b/client/strings/ru.json
index 3c95affa..f7f56965 100644
--- a/client/strings/ru.json
+++ b/client/strings/ru.json
@@ -266,6 +266,7 @@
"LabelHost": "Хост",
"LabelHour": "Часы",
"LabelIcon": "Иконка",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "Включать в список воспроизведения",
"LabelIncomplete": "Не завершен",
"LabelInProgress": "В процессе",
diff --git a/client/strings/zh-cn.json b/client/strings/zh-cn.json
index 09eb6708..1d7f90dd 100644
--- a/client/strings/zh-cn.json
+++ b/client/strings/zh-cn.json
@@ -266,6 +266,7 @@
"LabelHost": "主机",
"LabelHour": "小时",
"LabelIcon": "图标",
+ "LabelImageURLFromTheWeb": "Image URL from the web",
"LabelIncludeInTracklist": "包含在音轨列表中",
"LabelIncomplete": "未听完",
"LabelInProgress": "正在听",
diff --git a/package-lock.json b/package-lock.json
index 77948004..7178ac98 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
"sequelize": "^6.32.1",
"socket.io": "^4.5.4",
"sqlite3": "^5.1.6",
+ "ssrf-req-filter": "^1.1.0",
"xml2js": "^0.5.0"
},
"bin": {
@@ -2387,6 +2388,22 @@
}
}
},
+ "node_modules/ssrf-req-filter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/ssrf-req-filter/-/ssrf-req-filter-1.1.0.tgz",
+ "integrity": "sha512-YUyTinAEm52NsoDvkTFN9BQIa5nURNr2aN0BwOiJxHK3tlyGUczHa+2LjcibKNugAk/losB6kXOfcRzy0LQ4uA==",
+ "dependencies": {
+ "ipaddr.js": "^2.1.0"
+ }
+ },
+ "node_modules/ssrf-req-filter/node_modules/ipaddr.js": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
+ "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
@@ -4437,6 +4454,21 @@
"tar": "^6.1.11"
}
},
+ "ssrf-req-filter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/ssrf-req-filter/-/ssrf-req-filter-1.1.0.tgz",
+ "integrity": "sha512-YUyTinAEm52NsoDvkTFN9BQIa5nURNr2aN0BwOiJxHK3tlyGUczHa+2LjcibKNugAk/losB6kXOfcRzy0LQ4uA==",
+ "requires": {
+ "ipaddr.js": "^2.1.0"
+ },
+ "dependencies": {
+ "ipaddr.js": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
+ "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ=="
+ }
+ }
+ },
"ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
@@ -4672,4 +4704,4 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index 4a00fa59..e76147d8 100644
--- a/package.json
+++ b/package.json
@@ -39,9 +39,10 @@
"sequelize": "^6.32.1",
"socket.io": "^4.5.4",
"sqlite3": "^5.1.6",
+ "ssrf-req-filter": "^1.1.0",
"xml2js": "^0.5.0"
},
"devDependencies": {
"nodemon": "^2.0.20"
}
-}
\ No newline at end of file
+}
diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js
index ac019a96..b0ecf446 100644
--- a/server/controllers/LibraryItemController.js
+++ b/server/controllers/LibraryItemController.js
@@ -182,22 +182,22 @@ class LibraryItemController {
return res.sendStatus(403)
}
- var libraryItem = req.libraryItem
+ let libraryItem = req.libraryItem
- var result = null
- if (req.body && req.body.url) {
+ let result = null
+ if (req.body?.url) {
Logger.debug(`[LibraryItemController] Requesting download cover from url "${req.body.url}"`)
result = await CoverManager.downloadCoverFromUrl(libraryItem, req.body.url)
- } else if (req.files && req.files.cover) {
+ } else if (req.files?.cover) {
Logger.debug(`[LibraryItemController] Handling uploaded cover`)
result = await CoverManager.uploadCover(libraryItem, req.files.cover)
} else {
return res.status(400).send('Invalid request no file or url')
}
- if (result && result.error) {
+ if (result?.error) {
return res.status(400).send(result.error)
- } else if (!result || !result.cover) {
+ } else if (!result?.cover) {
return res.status(500).send('Unknown error occurred')
}
diff --git a/server/managers/CoverManager.js b/server/managers/CoverManager.js
index f30c9c6d..934deaff 100644
--- a/server/managers/CoverManager.js
+++ b/server/managers/CoverManager.js
@@ -120,13 +120,16 @@ class CoverManager {
await fs.ensureDir(coverDirPath)
var temppath = Path.posix.join(coverDirPath, 'cover')
- var success = await downloadFile(url, temppath).then(() => true).catch((err) => {
- Logger.error(`[CoverManager] Download image file failed for "${url}"`, err)
+
+ let errorMsg = ''
+ let success = await downloadFile(url, temppath).then(() => true).catch((err) => {
+ errorMsg = err.message || 'Unknown error'
+ Logger.error(`[CoverManager] Download image file failed for "${url}"`, errorMsg)
return false
})
if (!success) {
return {
- error: 'Failed to download image from url'
+ error: 'Failed to download image from url: ' + errorMsg
}
}
diff --git a/server/utils/fileUtils.js b/server/utils/fileUtils.js
index 966c7a93..37e89029 100644
--- a/server/utils/fileUtils.js
+++ b/server/utils/fileUtils.js
@@ -1,7 +1,8 @@
-const fs = require('../libs/fsExtra')
-const rra = require('../libs/recursiveReaddirAsync')
const axios = require('axios')
const Path = require('path')
+const ssrfFilter = require('ssrf-req-filter')
+const fs = require('../libs/fsExtra')
+const rra = require('../libs/recursiveReaddirAsync')
const Logger = require('../Logger')
const { AudioMimeType } = require('./constants')
@@ -210,7 +211,9 @@ module.exports.downloadFile = (url, filepath) => {
url,
method: 'GET',
responseType: 'stream',
- timeout: 30000
+ timeout: 30000,
+ httpAgent: ssrfFilter(url),
+ httpsAgent: ssrfFilter(url)
}).then((response) => {
const writer = fs.createWriteStream(filepath)
response.data.pipe(writer)