diff --git a/frontend/src/api/files.js b/frontend/src/api/files.js index 7494e55b..6cae2359 100644 --- a/frontend/src/api/files.js +++ b/frontend/src/api/files.js @@ -1,4 +1,4 @@ -import { fetchURL, removePrefix } from "./utils"; +import { fetchURL, removePrefix, createURL } from "./utils"; import { baseURL } from "@/utils/constants"; import store from "@/store"; @@ -154,3 +154,33 @@ export async function checksum(url, algo) { const data = await resourceAction(`${url}?checksum=${algo}`, "GET"); return (await data.json()).checksums[algo]; } + +export function getDownloadURL(file, inline) { + const params = { + ...(inline && { inline: "true" }), + }; + + return createURL("api/raw" + file.path, params); +} + +export function getPreviewURL(file, size) { + const params = { + inline: "true", + key: Date.parse(file.modified), + }; + + return createURL("api/preview/" + size + file.path, params); +} + +export function getSubtitlesURL(file) { + const params = { + inline: "true", + }; + + const subtitles = []; + for (const sub of file.subtitles) { + subtitles.push(createURL("api/raw" + sub, params)); + } + + return subtitles; +} diff --git a/frontend/src/api/pub.js b/frontend/src/api/pub.js index 58eb1eb6..626571b3 100644 --- a/frontend/src/api/pub.js +++ b/frontend/src/api/pub.js @@ -1,4 +1,4 @@ -import { fetchURL, removePrefix } from "./utils"; +import { fetchURL, removePrefix, createURL } from "./utils"; import { baseURL } from "@/utils/constants"; export async function fetch(url, password = "") { @@ -59,3 +59,12 @@ export function download(format, hash, token, ...files) { window.open(url); } + +export function getDownloadURL(share, inline = false) { + const params = { + ...(inline && { inline: "true" }), + ...(share.token && { token: share.token }), + }; + + return createURL("api/public/dl/" + share.hash + share.path, params, false); +} diff --git a/frontend/src/api/share.js b/frontend/src/api/share.js index 54bbc460..29dfe877 100644 --- a/frontend/src/api/share.js +++ b/frontend/src/api/share.js @@ -1,4 +1,4 @@ -import { fetchURL, fetchJSON, removePrefix } from "./utils"; +import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils"; export async function list() { return fetchJSON("/api/shares"); @@ -34,3 +34,7 @@ export async function create(url, password = "", expires = "", unit = "hours") { body: body, }); } + +export function getShareURL(share) { + return createURL("share/" + share.hash, {}, false); +} diff --git a/frontend/src/api/utils.js b/frontend/src/api/utils.js index 65c6740a..f9fc9023 100644 --- a/frontend/src/api/utils.js +++ b/frontend/src/api/utils.js @@ -1,6 +1,7 @@ import store from "@/store"; import { renew } from "@/utils/auth"; import { baseURL } from "@/utils/constants"; +import { encodePath } from "@/utils/url"; export async function fetchURL(url, opts) { opts = opts || {}; @@ -45,3 +46,18 @@ export function removePrefix(url) { if (url[0] !== "/") url = "/" + url; return url; } + +export function createURL(endpoint, params = {}, auth = true) { + const url = new URL(encodePath(endpoint), origin + baseURL); + + const searchParams = { + ...(auth && { auth: store.state.jwt }), + ...params, + }; + + for (const key in searchParams) { + url.searchParams.set(key, searchParams[key]); + } + + return url.toString(); +} diff --git a/frontend/src/components/files/ListingItem.vue b/frontend/src/components/files/ListingItem.vue index 351fba1f..4495be82 100644 --- a/frontend/src/components/files/ListingItem.vue +++ b/frontend/src/components/files/ListingItem.vue @@ -35,7 +35,7 @@ </template> <script> -import { baseURL, enableThumbs } from "@/utils/constants"; +import { enableThumbs } from "@/utils/constants"; import { mapMutations, mapGetters, mapState } from "vuex"; import filesize from "filesize"; import moment from "moment"; @@ -58,6 +58,7 @@ export default { "modified", "index", "readOnly", + "path", ], computed: { ...mapState(["user", "selected", "req", "jwt"]), @@ -83,12 +84,12 @@ export default { return true; }, thumbnailUrl() { - const path = this.url.replace(/^\/files\//, ""); + const file = { + path: this.path, + modified: this.modified, + }; - // reload the image when the file is replaced - const key = Date.parse(this.modified); - - return `${baseURL}/api/preview/thumb/${path}?k=${key}&inline=true`; + return api.getPreviewURL(file, "thumb"); }, isThumbsEnabled() { return enableThumbs; diff --git a/frontend/src/components/prompts/Share.vue b/frontend/src/components/prompts/Share.vue index eaedafda..210b2f93 100644 --- a/frontend/src/components/prompts/Share.vue +++ b/frontend/src/components/prompts/Share.vue @@ -25,7 +25,7 @@ <td class="small"> <button class="action copy-clipboard" - :data-clipboard-text="buildLink(link.hash)" + :data-clipboard-text="buildLink(link)" :aria-label="$t('buttons.copyToClipboard')" :title="$t('buttons.copyToClipboard')" > @@ -118,7 +118,6 @@ <script> import { mapState, mapGetters } from "vuex"; import { share as api } from "@/api"; -import { baseURL } from "@/utils/constants"; import moment from "moment"; import Clipboard from "clipboard"; @@ -213,8 +212,8 @@ export default { humanTime(time) { return moment(time * 1000).fromNow(); }, - buildLink(hash) { - return `${window.location.origin}${baseURL}/share/${hash}`; + buildLink(share) { + return api.getShareURL(share); }, sort() { this.links = this.links.sort((a, b) => { diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index 9761339b..200c4e8d 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -14,6 +14,7 @@ const theme = window.FileBrowser.Theme; const enableThumbs = window.FileBrowser.EnableThumbs; const resizePreview = window.FileBrowser.ResizePreview; const enableExec = window.FileBrowser.EnableExec; +const origin = window.location.origin; export { name, @@ -31,4 +32,5 @@ export { enableThumbs, resizePreview, enableExec, + origin, }; diff --git a/frontend/src/utils/url.js b/frontend/src/utils/url.js index 8346bb03..33d124a2 100644 --- a/frontend/src/utils/url.js +++ b/frontend/src/utils/url.js @@ -1,4 +1,4 @@ -function removeLastDir(url) { +export function removeLastDir(url) { var arr = url.split("/"); if (arr.pop() === "") { arr.pop(); @@ -9,7 +9,7 @@ function removeLastDir(url) { // this code borrow from mozilla // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples -function encodeRFC5987ValueChars(str) { +export function encodeRFC5987ValueChars(str) { return ( encodeURIComponent(str) // Note that although RFC3986 reserves "!", RFC5987 does not, @@ -22,7 +22,7 @@ function encodeRFC5987ValueChars(str) { ); } -function encodePath(str) { +export function encodePath(str) { return str .split("/") .map((v) => encodeURIComponent(v)) @@ -30,7 +30,7 @@ function encodePath(str) { } export default { - encodeRFC5987ValueChars: encodeRFC5987ValueChars, - removeLastDir: removeLastDir, - encodePath: encodePath, + encodeRFC5987ValueChars, + removeLastDir, + encodePath, }; diff --git a/frontend/src/views/Share.vue b/frontend/src/views/Share.vue index eefc6453..f26b4fc2 100644 --- a/frontend/src/views/Share.vue +++ b/frontend/src/views/Share.vue @@ -104,7 +104,7 @@ </a> </div> <div class="share__box__element share__box__center"> - <qrcode-vue :value="fullLink" size="200" level="M"></qrcode-vue> + <qrcode-vue :value="link" size="200" level="M"></qrcode-vue> </div> </div> <div @@ -173,7 +173,6 @@ <script> import { mapState, mapMutations, mapGetters } from "vuex"; import { pub as api } from "@/api"; -import { baseURL } from "@/utils/constants"; import filesize from "filesize"; import moment from "moment"; @@ -231,21 +230,10 @@ export default { return "insert_drive_file"; }, link: function () { - let queryArg = ""; - if (this.token !== "") { - queryArg = `?token=${this.token}`; - } - - const path = this.$route.path.split("/").splice(2).join("/"); - return `${baseURL}/api/public/dl/${path}${queryArg}`; + return api.getDownloadURL(this.req); }, inlineLink: function () { - let url = new URL(this.fullLink); - url.searchParams.set("inline", "true"); - return url.href; - }, - fullLink: function () { - return window.location.origin + this.link; + return api.getDownloadURL(this.req, true); }, humanSize: function () { if (this.req.isDir) { @@ -287,6 +275,7 @@ export default { try { let file = await api.fetch(url, this.password); + file.hash = this.hash; this.token = file.token || ""; diff --git a/frontend/src/views/files/Listing.vue b/frontend/src/views/files/Listing.vue index 4286e8dc..bdf4806b 100644 --- a/frontend/src/views/files/Listing.vue +++ b/frontend/src/views/files/Listing.vue @@ -209,6 +209,7 @@ v-bind:modified="item.modified" v-bind:type="item.type" v-bind:size="item.size" + v-bind:path="item.path" > </item> </div> @@ -225,6 +226,7 @@ v-bind:modified="item.modified" v-bind:type="item.type" v-bind:size="item.size" + v-bind:path="item.path" > </item> </div> diff --git a/frontend/src/views/files/Preview.vue b/frontend/src/views/files/Preview.vue index 4e699dca..d08ac5aa 100644 --- a/frontend/src/views/files/Preview.vue +++ b/frontend/src/views/files/Preview.vue @@ -103,7 +103,7 @@ </a> <a target="_blank" - :href="downloadUrl + '&inline=true'" + :href="raw" class="button button--flat" v-if="!req.isDir" > @@ -145,7 +145,7 @@ <script> import { mapState } from "vuex"; import { files as api } from "@/api"; -import { baseURL, resizePreview } from "@/utils/constants"; +import { resizePreview } from "@/utils/constants"; import url from "@/utils/url"; import throttle from "lodash.throttle"; import HeaderBar from "@/components/header/HeaderBar"; @@ -186,23 +186,14 @@ export default { return this.nextLink !== ""; }, downloadUrl() { - return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${ - this.jwt - }`; - }, - previewUrl() { - // reload the image when the file is replaced - const key = Date.parse(this.req.modified); - - if (this.req.type === "image" && !this.fullSize) { - return `${baseURL}/api/preview/big${url.encodePath( - this.req.path - )}?k=${key}`; - } - return `${baseURL}/api/raw${url.encodePath(this.req.path)}?k=${key}`; + return api.getDownloadURL(this.req); }, raw() { - return `${this.previewUrl}&inline=true`; + if (this.req.type === "image" && !this.fullSize) { + return api.getPreviewURL(this.req, "big"); + } + + return api.getDownloadURL(this.req, true); }, showMore() { return this.$store.state.show === "more"; @@ -276,9 +267,7 @@ export default { } if (this.req.subtitles) { - this.subtitles = this.req.subtitles.map( - (sub) => `${baseURL}/api/raw${sub}?inline=true` - ); + this.subtitles = api.getSubtitlesURL(this.req); } let dirs = this.$route.fullPath.split("/"); @@ -320,15 +309,14 @@ export default { return; } }, - prefetchUrl: function (item) { - const key = Date.parse(item.modified); - if (item.type === "image" && !this.fullSize) { - return `${baseURL}/api/preview/big${item.path}?k=${key}&inline=true`; - } else if (item.type === "image") { - return `${baseURL}/api/raw${item.path}?k=${key}&inline=true`; - } else { + prefetchUrl(item) { + if (item.type !== "image") { return ""; } + + return this.fullSize + ? api.getDownloadURL(item, true) + : api.getPreviewURL(item, "big"); }, openMore() { this.$store.commit("showHover", "more"); @@ -358,7 +346,7 @@ export default { this.$router.push({ path: uri }); }, download() { - api.download(null, this.$route.path); + window.open(this.downloadUrl); }, }, }; diff --git a/frontend/src/views/settings/Shares.vue b/frontend/src/views/settings/Shares.vue index b052c285..b27b4f7a 100644 --- a/frontend/src/views/settings/Shares.vue +++ b/frontend/src/views/settings/Shares.vue @@ -19,9 +19,7 @@ <tr v-for="link in links" :key="link.hash"> <td> - <a :href="buildLink(link.hash)" target="_blank">{{ - link.path - }}</a> + <a :href="buildLink(link)" target="_blank">{{ link.path }}</a> </td> <td> <template v-if="link.expire !== 0">{{ @@ -43,7 +41,7 @@ <td class="small"> <button class="action copy-clipboard" - :data-clipboard-text="buildLink(link.hash)" + :data-clipboard-text="buildLink(link)" :aria-label="$t('buttons.copyToClipboard')" :title="$t('buttons.copyToClipboard')" > @@ -64,7 +62,6 @@ <script> import { share as api, users } from "@/api"; -import { baseURL } from "@/utils/constants"; import { mapState, mapMutations } from "vuex"; import moment from "moment"; import Clipboard from "clipboard"; @@ -136,8 +133,8 @@ export default { humanTime(time) { return moment(time * 1000).fromNow(); }, - buildLink(hash) { - return `${window.location.origin}${baseURL}/share/${hash}`; + buildLink(share) { + return api.getShareURL(share); }, }, };