Merge pull request #1942 from ramiresviana/fixes-12

pull/1950/head
Oleg Lobanov 2022-05-13 10:42:51 +02:00 committed by GitHub
commit 1d66bbe40a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 233 additions and 191 deletions

View File

@ -322,7 +322,7 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
continue continue
} }
isSymlink := false isSymlink, isInvalidLink := false, false
if IsSymlink(f.Mode()) { if IsSymlink(f.Mode()) {
isSymlink = true isSymlink = true
// It's a symbolic link. We try to follow it. If it doesn't work, // It's a symbolic link. We try to follow it. If it doesn't work,
@ -330,6 +330,8 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
info, err := i.Fs.Stat(fPath) info, err := i.Fs.Stat(fPath)
if err == nil { if err == nil {
f = info f = info
} else {
isInvalidLink = true
} }
} }
@ -350,9 +352,13 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
} else { } else {
listing.NumFiles++ listing.NumFiles++
err := file.detectType(true, false, readHeader) if isInvalidLink {
if err != nil { file.Type = "invalid_link"
return err } else {
err := file.detectType(true, false, readHeader)
if err != nil {
return err
}
} }
} }

View File

@ -1,4 +1,4 @@
import { fetchURL, removePrefix } from "./utils"; import { fetchURL, removePrefix, createURL } from "./utils";
import { baseURL } from "@/utils/constants"; import { baseURL } from "@/utils/constants";
import store from "@/store"; import store from "@/store";
@ -7,28 +7,24 @@ export async function fetch(url) {
const res = await fetchURL(`/api/resources${url}`, {}); const res = await fetchURL(`/api/resources${url}`, {});
if (res.status === 200) { let data = await res.json();
let data = await res.json(); data.url = `/files${url}`;
data.url = `/files${url}`;
if (data.isDir) { if (data.isDir) {
if (!data.url.endsWith("/")) data.url += "/"; if (!data.url.endsWith("/")) data.url += "/";
data.items = data.items.map((item, index) => { data.items = data.items.map((item, index) => {
item.index = index; item.index = index;
item.url = `${data.url}${encodeURIComponent(item.name)}`; item.url = `${data.url}${encodeURIComponent(item.name)}`;
if (item.isDir) { if (item.isDir) {
item.url += "/"; item.url += "/";
} }
return item; return item;
}); });
}
return data;
} else {
throw new Error(res.status);
} }
return data;
} }
async function resourceAction(url, method, content) { async function resourceAction(url, method, content) {
@ -42,11 +38,7 @@ async function resourceAction(url, method, content) {
const res = await fetchURL(`/api/resources${url}`, opts); const res = await fetchURL(`/api/resources${url}`, opts);
if (res.status !== 200) { return res;
throw new Error(await res.text());
} else {
return res;
}
} }
export async function remove(url) { export async function remove(url) {
@ -119,8 +111,8 @@ export async function post(url, content = "", overwrite = false, onupload) {
} }
}; };
request.onerror = (error) => { request.onerror = () => {
reject(error); reject(new Error("001 Connection aborted"));
}; };
request.send(bufferContent || content); request.send(bufferContent || content);
@ -154,3 +146,33 @@ export async function checksum(url, algo) {
const data = await resourceAction(`${url}?checksum=${algo}`, "GET"); const data = await resourceAction(`${url}?checksum=${algo}`, "GET");
return (await data.json()).checksums[algo]; 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;
}

View File

@ -1,35 +1,35 @@
import { fetchURL, removePrefix } from "./utils"; import { fetchURL, removePrefix, createURL } from "./utils";
import { baseURL } from "@/utils/constants"; import { baseURL } from "@/utils/constants";
export async function fetch(url, password = "") { export async function fetch(url, password = "") {
url = removePrefix(url); url = removePrefix(url);
const res = await fetchURL(`/api/public/share${url}`, { const res = await fetchURL(
headers: { "X-SHARE-PASSWORD": encodeURIComponent(password) }, `/api/public/share${url}`,
}); {
headers: { "X-SHARE-PASSWORD": encodeURIComponent(password) },
},
false
);
if (res.status === 200) { let data = await res.json();
let data = await res.json(); data.url = `/share${url}`;
data.url = `/share${url}`;
if (data.isDir) { if (data.isDir) {
if (!data.url.endsWith("/")) data.url += "/"; if (!data.url.endsWith("/")) data.url += "/";
data.items = data.items.map((item, index) => { data.items = data.items.map((item, index) => {
item.index = index; item.index = index;
item.url = `${data.url}${encodeURIComponent(item.name)}`; item.url = `${data.url}${encodeURIComponent(item.name)}`;
if (item.isDir) { if (item.isDir) {
item.url += "/"; item.url += "/";
} }
return item; return item;
}); });
}
return data;
} else {
throw new Error(res.status);
} }
return data;
} }
export function download(format, hash, token, ...files) { export function download(format, hash, token, ...files) {
@ -59,3 +59,12 @@ export function download(format, hash, token, ...files) {
window.open(url); 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);
}

View File

@ -11,21 +11,17 @@ export default async function search(base, query) {
let res = await fetchURL(`/api/search${base}?query=${query}`, {}); let res = await fetchURL(`/api/search${base}?query=${query}`, {});
if (res.status === 200) { let data = await res.json();
let data = await res.json();
data = data.map((item) => { data = data.map((item) => {
item.url = `/files${base}` + url.encodePath(item.path); item.url = `/files${base}` + url.encodePath(item.path);
if (item.dir) { if (item.dir) {
item.url += "/"; item.url += "/";
} }
return item; return item;
}); });
return data; return data;
} else {
throw Error(res.status);
}
} }

View File

@ -5,12 +5,8 @@ export function get() {
} }
export async function update(settings) { export async function update(settings) {
const res = await fetchURL(`/api/settings`, { await fetchURL(`/api/settings`, {
method: "PUT", method: "PUT",
body: JSON.stringify(settings), body: JSON.stringify(settings),
}); });
if (res.status !== 200) {
throw new Error(res.status);
}
} }

View File

@ -1,4 +1,4 @@
import { fetchURL, fetchJSON, removePrefix } from "./utils"; import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils";
export async function list() { export async function list() {
return fetchJSON("/api/shares"); return fetchJSON("/api/shares");
@ -10,13 +10,9 @@ export async function get(url) {
} }
export async function remove(hash) { export async function remove(hash) {
const res = await fetchURL(`/api/share/${hash}`, { await fetchURL(`/api/share/${hash}`, {
method: "DELETE", method: "DELETE",
}); });
if (res.status !== 200) {
throw new Error(res.status);
}
} }
export async function create(url, password = "", expires = "", unit = "hours") { export async function create(url, password = "", expires = "", unit = "hours") {
@ -34,3 +30,7 @@ export async function create(url, password = "", expires = "", unit = "hours") {
body: body, body: body,
}); });
} }
export function getShareURL(share) {
return createURL("share/" + share.hash, {}, false);
}

View File

@ -20,13 +20,11 @@ export async function create(user) {
if (res.status === 201) { if (res.status === 201) {
return res.headers.get("Location"); return res.headers.get("Location");
} else {
throw new Error(res.status);
} }
} }
export async function update(user, which = ["all"]) { export async function update(user, which = ["all"]) {
const res = await fetchURL(`/api/users/${user.id}`, { await fetchURL(`/api/users/${user.id}`, {
method: "PUT", method: "PUT",
body: JSON.stringify({ body: JSON.stringify({
what: "user", what: "user",
@ -34,18 +32,10 @@ export async function update(user, which = ["all"]) {
data: user, data: user,
}), }),
}); });
if (res.status !== 200) {
throw new Error(res.status);
}
} }
export async function remove(id) { export async function remove(id) {
const res = await fetchURL(`/api/users/${id}`, { await fetchURL(`/api/users/${id}`, {
method: "DELETE", method: "DELETE",
}); });
if (res.status !== 200) {
throw new Error(res.status);
}
} }

View File

@ -1,8 +1,9 @@
import store from "@/store"; import store from "@/store";
import { renew } from "@/utils/auth"; import { renew, logout } from "@/utils/auth";
import { baseURL } from "@/utils/constants"; import { baseURL } from "@/utils/constants";
import { encodePath } from "@/utils/url";
export async function fetchURL(url, opts) { export async function fetchURL(url, opts, auth = true) {
opts = opts || {}; opts = opts || {};
opts.headers = opts.headers || {}; opts.headers = opts.headers || {};
@ -17,14 +18,28 @@ export async function fetchURL(url, opts) {
}, },
...rest, ...rest,
}); });
} catch (error) { } catch {
return { status: 0 }; const error = new Error("000 No connection");
error.status = 0;
throw error;
} }
if (res.headers.get("X-Renew-Token") === "true") { if (auth && res.headers.get("X-Renew-Token") === "true") {
await renew(store.state.jwt); await renew(store.state.jwt);
} }
if (res.status < 200 || res.status > 299) {
const error = new Error(await res.text());
error.status = res.status;
if (auth && res.status == 401) {
logout();
}
throw error;
}
return res; return res;
} }
@ -45,3 +60,18 @@ export function removePrefix(url) {
if (url[0] !== "/") url = "/" + url; if (url[0] !== "/") url = "/" + url;
return 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();
}

View File

@ -35,7 +35,7 @@
</template> </template>
<script> <script>
import { baseURL, enableThumbs } from "@/utils/constants"; import { enableThumbs } from "@/utils/constants";
import { mapMutations, mapGetters, mapState } from "vuex"; import { mapMutations, mapGetters, mapState } from "vuex";
import filesize from "filesize"; import filesize from "filesize";
import moment from "moment"; import moment from "moment";
@ -58,6 +58,7 @@ export default {
"modified", "modified",
"index", "index",
"readOnly", "readOnly",
"path",
], ],
computed: { computed: {
...mapState(["user", "selected", "req", "jwt"]), ...mapState(["user", "selected", "req", "jwt"]),
@ -83,12 +84,12 @@ export default {
return true; return true;
}, },
thumbnailUrl() { thumbnailUrl() {
const path = this.url.replace(/^\/files\//, ""); const file = {
path: this.path,
modified: this.modified,
};
// reload the image when the file is replaced return api.getPreviewURL(file, "thumb");
const key = Date.parse(this.modified);
return `${baseURL}/api/preview/thumb/${path}?k=${key}&inline=true`;
}, },
isThumbsEnabled() { isThumbsEnabled() {
return enableThumbs; return enableThumbs;
@ -97,7 +98,7 @@ export default {
methods: { methods: {
...mapMutations(["addSelected", "removeSelected", "resetSelected"]), ...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
humanSize: function () { humanSize: function () {
return filesize(this.size); return this.type == "invalid_link" ? "invalid link" : filesize(this.size);
}, },
humanTime: function () { humanTime: function () {
if (this.readOnly == undefined && this.user.dateFormat) { if (this.readOnly == undefined && this.user.dateFormat) {

View File

@ -25,7 +25,7 @@
<td class="small"> <td class="small">
<button <button
class="action copy-clipboard" class="action copy-clipboard"
:data-clipboard-text="buildLink(link.hash)" :data-clipboard-text="buildLink(link)"
:aria-label="$t('buttons.copyToClipboard')" :aria-label="$t('buttons.copyToClipboard')"
:title="$t('buttons.copyToClipboard')" :title="$t('buttons.copyToClipboard')"
> >
@ -118,7 +118,6 @@
<script> <script>
import { mapState, mapGetters } from "vuex"; import { mapState, mapGetters } from "vuex";
import { share as api } from "@/api"; import { share as api } from "@/api";
import { baseURL } from "@/utils/constants";
import moment from "moment"; import moment from "moment";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
@ -213,8 +212,8 @@ export default {
humanTime(time) { humanTime(time) {
return moment(time * 1000).fromNow(); return moment(time * 1000).fromNow();
}, },
buildLink(hash) { buildLink(share) {
return `${window.location.origin}${baseURL}/share/${hash}`; return api.getShareURL(share);
}, },
sort() { sort() {
this.links = this.links.sort((a, b) => { this.links = this.links.sort((a, b) => {

View File

@ -25,6 +25,7 @@
class="file" class="file"
v-for="file in filesInUpload" v-for="file in filesInUpload"
:key="file.id" :key="file.id"
:data-dir="file.isDir"
:data-type="file.type" :data-type="file.type"
:aria-label="file.name" :aria-label="file.name"
> >

View File

@ -11,6 +11,7 @@
.file-icons [data-type=pdf] i::before { content: 'description' } .file-icons [data-type=pdf] i::before { content: 'description' }
.file-icons [data-type=text] i::before { content: 'description' } .file-icons [data-type=text] i::before { content: 'description' }
.file-icons [data-type=video] i::before { content: 'movie' } .file-icons [data-type=video] i::before { content: 'movie' }
.file-icons [data-type=invalid_link] i::before { content: 'link_off' }
/* #f90 - Image */ /* #f90 - Image */
@ -125,6 +126,7 @@
.file-icons [data-type=audio] i { color: var(--icon-yellow) } .file-icons [data-type=audio] i { color: var(--icon-yellow) }
.file-icons [data-type=image] i { color: var(--icon-orange) } .file-icons [data-type=image] i { color: var(--icon-orange) }
.file-icons [data-type=video] i { color: var(--icon-violet) } .file-icons [data-type=video] i { color: var(--icon-violet) }
.file-icons [data-type=invalid_link] i { color: var(--icon-red) }
/* #f00 - Adobe/Oracle */ /* #f00 - Adobe/Oracle */

View File

@ -13,9 +13,25 @@ import Shares from "@/views/settings/Shares";
import Errors from "@/views/Errors"; import Errors from "@/views/Errors";
import store from "@/store"; import store from "@/store";
import { baseURL, name } from "@/utils/constants"; import { baseURL, name } from "@/utils/constants";
import i18n from "@/i18n";
Vue.use(Router); Vue.use(Router);
const titles = {
Login: "sidebar.login",
Share: "buttons.share",
Files: "files.files",
Settings: "sidebar.settings",
ProfileSettings: "settings.profileSettings",
Shares: "settings.shareManagement",
GlobalSettings: "settings.globalSettings",
Users: "settings.users",
User: "settings.user",
Forbidden: "errors.forbidden",
NotFound: "errors.notFound",
InternalServerError: "errors.internal",
};
const router = new Router({ const router = new Router({
base: baseURL, base: baseURL,
mode: "history", mode: "history",
@ -29,7 +45,6 @@ const router = new Router({
return next({ path: "/files" }); return next({ path: "/files" });
} }
document.title = "Login - " + name;
next(); next();
}, },
}, },
@ -63,7 +78,7 @@ const router = new Router({
children: [ children: [
{ {
path: "/settings/profile", path: "/settings/profile",
name: "Profile Settings", name: "ProfileSettings",
component: ProfileSettings, component: ProfileSettings,
}, },
{ {
@ -73,7 +88,7 @@ const router = new Router({
}, },
{ {
path: "/settings/global", path: "/settings/global",
name: "Global Settings", name: "GlobalSettings",
component: GlobalSettings, component: GlobalSettings,
meta: { meta: {
requiresAdmin: true, requiresAdmin: true,
@ -108,7 +123,7 @@ const router = new Router({
}, },
{ {
path: "/404", path: "/404",
name: "Not Found", name: "NotFound",
component: Errors, component: Errors,
props: { props: {
errorCode: 404, errorCode: 404,
@ -117,7 +132,7 @@ const router = new Router({
}, },
{ {
path: "/500", path: "/500",
name: "Internal Server Error", name: "InternalServerError",
component: Errors, component: Errors,
props: { props: {
errorCode: 500, errorCode: 500,
@ -140,7 +155,8 @@ const router = new Router({
}); });
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
document.title = to.name + " - " + name; const title = i18n.t(titles[to.name]);
document.title = title + " - " + name;
if (to.matched.some((record) => record.meta.requiresAuth)) { if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!store.getters.isLogged) { if (!store.getters.isLogged) {

View File

@ -25,15 +25,19 @@ const getters = {
let upload = state.upload.uploads[index]; let upload = state.upload.uploads[index];
let id = upload.id; let id = upload.id;
let type = upload.type; let type = upload.type;
let name = decodeURIComponent(upload.path.replace(/^.*[\\/]/, "")); let name = upload.file.name;
let progress = state.upload.progress[id];
let size = state.upload.sizes[id]; let size = state.upload.sizes[id];
let isDir = upload.file.isDir;
let progress = isDir
? 100
: Math.ceil((state.upload.progress[id] / size) * 100);
files.push({ files.push({
id, id,
name, name,
progress: Math.ceil((progress / size) * 100), progress,
type, type,
isDir,
}); });
} }

View File

@ -14,6 +14,7 @@ const theme = window.FileBrowser.Theme;
const enableThumbs = window.FileBrowser.EnableThumbs; const enableThumbs = window.FileBrowser.EnableThumbs;
const resizePreview = window.FileBrowser.ResizePreview; const resizePreview = window.FileBrowser.ResizePreview;
const enableExec = window.FileBrowser.EnableExec; const enableExec = window.FileBrowser.EnableExec;
const origin = window.location.origin;
export { export {
name, name,
@ -31,4 +32,5 @@ export {
enableThumbs, enableThumbs,
resizePreview, resizePreview,
enableExec, enableExec,
origin,
}; };

View File

@ -70,6 +70,7 @@ export function scanFiles(dt) {
isDir: true, isDir: true,
size: 0, size: 0,
fullPath: `${directory}${entry.name}`, fullPath: `${directory}${entry.name}`,
name: entry.name,
}; };
contents.push(dir); contents.push(dir);
@ -129,7 +130,7 @@ export function handleFiles(files, base, overwrite = false) {
path, path,
file, file,
overwrite, overwrite,
type: detectType(file.type), ...(!file.isDir && { type: detectType(file.type) }),
}; };
store.dispatch("upload/upload", item); store.dispatch("upload/upload", item);

View File

@ -1,4 +1,4 @@
function removeLastDir(url) { export function removeLastDir(url) {
var arr = url.split("/"); var arr = url.split("/");
if (arr.pop() === "") { if (arr.pop() === "") {
arr.pop(); arr.pop();
@ -9,7 +9,7 @@ function removeLastDir(url) {
// this code borrow from mozilla // this code borrow from mozilla
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples
function encodeRFC5987ValueChars(str) { export function encodeRFC5987ValueChars(str) {
return ( return (
encodeURIComponent(str) encodeURIComponent(str)
// Note that although RFC3986 reserves "!", RFC5987 does not, // Note that although RFC3986 reserves "!", RFC5987 does not,
@ -22,7 +22,7 @@ function encodeRFC5987ValueChars(str) {
); );
} }
function encodePath(str) { export function encodePath(str) {
return str return str
.split("/") .split("/")
.map((v) => encodeURIComponent(v)) .map((v) => encodeURIComponent(v))
@ -30,7 +30,7 @@ function encodePath(str) {
} }
export default { export default {
encodeRFC5987ValueChars: encodeRFC5987ValueChars, encodeRFC5987ValueChars,
removeLastDir: removeLastDir, removeLastDir,
encodePath: encodePath, encodePath,
}; };

View File

@ -38,15 +38,8 @@ export default {
}, },
props: ["errorCode", "showHeader"], props: ["errorCode", "showHeader"],
computed: { computed: {
code() {
return this.errorCode === "0" ||
this.errorCode === "404" ||
this.errorCode === "403"
? parseInt(this.errorCode)
: 500;
},
info() { info() {
return errors[this.code]; return errors[this.errorCode] ? errors[this.errorCode] : errors[500];
}, },
}, },
}; };

View File

@ -4,7 +4,7 @@
<breadcrumbs base="/files" /> <breadcrumbs base="/files" />
<errors v-if="error" :errorCode="error.message" /> <errors v-if="error" :errorCode="error.status" />
<component v-else-if="currentView" :is="currentView"></component> <component v-else-if="currentView" :is="currentView"></component>
<div v-else> <div v-else>
<h2 class="message delayed"> <h2 class="message delayed">
@ -28,7 +28,6 @@ import Breadcrumbs from "@/components/Breadcrumbs";
import Errors from "@/views/Errors"; import Errors from "@/views/Errors";
import Preview from "@/views/files/Preview"; import Preview from "@/views/files/Preview";
import Listing from "@/views/files/Listing"; import Listing from "@/views/files/Listing";
import { name } from "@/utils/constants";
function clean(path) { function clean(path) {
return path.endsWith("/") ? path.slice(0, -1) : path; return path.endsWith("/") ? path.slice(0, -1) : path;
@ -52,7 +51,6 @@ export default {
}, },
computed: { computed: {
...mapState(["req", "reload", "loading", "show"]), ...mapState(["req", "reload", "loading", "show"]),
name: () => name,
currentView() { currentView() {
if (this.req.type == undefined) { if (this.req.type == undefined) {
return null; return null;
@ -118,7 +116,7 @@ export default {
} }
this.$store.commit("updateRequest", res); this.$store.commit("updateRequest", res);
document.title = `${res.name} - ${this.$route.name} - ${this.name}`; document.title = `${res.name} - ${document.title}`;
} catch (e) { } catch (e) {
this.error = e; this.error = e;
} finally { } finally {

View File

@ -30,7 +30,7 @@
</h2> </h2>
</div> </div>
<div v-else-if="error"> <div v-else-if="error">
<div v-if="error.message === '401'"> <div v-if="error.status === 401">
<div class="card floating" id="password"> <div class="card floating" id="password">
<div v-if="attemptedPasswordLogin" class="share__wrong__password"> <div v-if="attemptedPasswordLogin" class="share__wrong__password">
{{ $t("login.wrongCredentials") }} {{ $t("login.wrongCredentials") }}
@ -60,7 +60,7 @@
</div> </div>
</div> </div>
</div> </div>
<errors v-else :errorCode="error.message" /> <errors v-else :errorCode="error.status" />
</div> </div>
<div v-else> <div v-else>
<div class="share"> <div class="share">
@ -104,7 +104,7 @@
</a> </a>
</div> </div>
<div class="share__box__element share__box__center"> <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> </div>
<div <div
@ -173,7 +173,6 @@
<script> <script>
import { mapState, mapMutations, mapGetters } from "vuex"; import { mapState, mapMutations, mapGetters } from "vuex";
import { pub as api } from "@/api"; import { pub as api } from "@/api";
import { baseURL } from "@/utils/constants";
import filesize from "filesize"; import filesize from "filesize";
import moment from "moment"; import moment from "moment";
@ -231,21 +230,10 @@ export default {
return "insert_drive_file"; return "insert_drive_file";
}, },
link: function () { link: function () {
let queryArg = ""; return api.getDownloadURL(this.req);
if (this.token !== "") {
queryArg = `?token=${this.token}`;
}
const path = this.$route.path.split("/").splice(2).join("/");
return `${baseURL}/api/public/dl/${path}${queryArg}`;
}, },
inlineLink: function () { inlineLink: function () {
let url = new URL(this.fullLink); return api.getDownloadURL(this.req, true);
url.searchParams.set("inline", "true");
return url.href;
},
fullLink: function () {
return window.location.origin + this.link;
}, },
humanSize: function () { humanSize: function () {
if (this.req.isDir) { if (this.req.isDir) {
@ -287,11 +275,12 @@ export default {
try { try {
let file = await api.fetch(url, this.password); let file = await api.fetch(url, this.password);
file.hash = this.hash;
this.token = file.token || ""; this.token = file.token || "";
this.updateRequest(file); this.updateRequest(file);
document.title = `${file.name} - ${this.$route.name}`; document.title = `${file.name} - ${document.title}`;
} catch (e) { } catch (e) {
this.error = e; this.error = e;
} finally { } finally {

View File

@ -209,6 +209,7 @@
v-bind:modified="item.modified" v-bind:modified="item.modified"
v-bind:type="item.type" v-bind:type="item.type"
v-bind:size="item.size" v-bind:size="item.size"
v-bind:path="item.path"
> >
</item> </item>
</div> </div>
@ -225,6 +226,7 @@
v-bind:modified="item.modified" v-bind:modified="item.modified"
v-bind:type="item.type" v-bind:type="item.type"
v-bind:size="item.size" v-bind:size="item.size"
v-bind:path="item.path"
> >
</item> </item>
</div> </div>

View File

@ -103,7 +103,7 @@
</a> </a>
<a <a
target="_blank" target="_blank"
:href="downloadUrl + '&inline=true'" :href="raw"
class="button button--flat" class="button button--flat"
v-if="!req.isDir" v-if="!req.isDir"
> >
@ -145,7 +145,7 @@
<script> <script>
import { mapState } from "vuex"; import { mapState } from "vuex";
import { files as api } from "@/api"; import { files as api } from "@/api";
import { baseURL, resizePreview } from "@/utils/constants"; import { resizePreview } from "@/utils/constants";
import url from "@/utils/url"; import url from "@/utils/url";
import throttle from "lodash.throttle"; import throttle from "lodash.throttle";
import HeaderBar from "@/components/header/HeaderBar"; import HeaderBar from "@/components/header/HeaderBar";
@ -186,23 +186,14 @@ export default {
return this.nextLink !== ""; return this.nextLink !== "";
}, },
downloadUrl() { downloadUrl() {
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${ return api.getDownloadURL(this.req);
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}`;
}, },
raw() { 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() { showMore() {
return this.$store.state.show === "more"; return this.$store.state.show === "more";
@ -276,9 +267,7 @@ export default {
} }
if (this.req.subtitles) { if (this.req.subtitles) {
this.subtitles = this.req.subtitles.map( this.subtitles = api.getSubtitlesURL(this.req);
(sub) => `${baseURL}/api/raw${sub}?inline=true`
);
} }
let dirs = this.$route.fullPath.split("/"); let dirs = this.$route.fullPath.split("/");
@ -320,15 +309,14 @@ export default {
return; return;
} }
}, },
prefetchUrl: function (item) { prefetchUrl(item) {
const key = Date.parse(item.modified); if (item.type !== "image") {
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 {
return ""; return "";
} }
return this.fullSize
? api.getDownloadURL(item, true)
: api.getPreviewURL(item, "big");
}, },
openMore() { openMore() {
this.$store.commit("showHover", "more"); this.$store.commit("showHover", "more");
@ -358,7 +346,7 @@ export default {
this.$router.push({ path: uri }); this.$router.push({ path: uri });
}, },
download() { download() {
api.download(null, this.$route.path); window.open(this.downloadUrl);
}, },
}, },
}; };

View File

@ -1,5 +1,5 @@
<template> <template>
<errors v-if="error" :errorCode="error.message" /> <errors v-if="error" :errorCode="error.status" />
<div class="row" v-else-if="!loading"> <div class="row" v-else-if="!loading">
<div class="column"> <div class="column">
<form class="card" @submit.prevent="save"> <form class="card" @submit.prevent="save">

View File

@ -1,5 +1,5 @@
<template> <template>
<errors v-if="error" :errorCode="error.message" /> <errors v-if="error" :errorCode="error.status" />
<div class="row" v-else-if="!loading"> <div class="row" v-else-if="!loading">
<div class="column"> <div class="column">
<div class="card"> <div class="card">
@ -19,9 +19,7 @@
<tr v-for="link in links" :key="link.hash"> <tr v-for="link in links" :key="link.hash">
<td> <td>
<a :href="buildLink(link.hash)" target="_blank">{{ <a :href="buildLink(link)" target="_blank">{{ link.path }}</a>
link.path
}}</a>
</td> </td>
<td> <td>
<template v-if="link.expire !== 0">{{ <template v-if="link.expire !== 0">{{
@ -43,7 +41,7 @@
<td class="small"> <td class="small">
<button <button
class="action copy-clipboard" class="action copy-clipboard"
:data-clipboard-text="buildLink(link.hash)" :data-clipboard-text="buildLink(link)"
:aria-label="$t('buttons.copyToClipboard')" :aria-label="$t('buttons.copyToClipboard')"
:title="$t('buttons.copyToClipboard')" :title="$t('buttons.copyToClipboard')"
> >
@ -64,7 +62,6 @@
<script> <script>
import { share as api, users } from "@/api"; import { share as api, users } from "@/api";
import { baseURL } from "@/utils/constants";
import { mapState, mapMutations } from "vuex"; import { mapState, mapMutations } from "vuex";
import moment from "moment"; import moment from "moment";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
@ -136,8 +133,8 @@ export default {
humanTime(time) { humanTime(time) {
return moment(time * 1000).fromNow(); return moment(time * 1000).fromNow();
}, },
buildLink(hash) { buildLink(share) {
return `${window.location.origin}${baseURL}/share/${hash}`; return api.getShareURL(share);
}, },
}, },
}; };

View File

@ -1,5 +1,5 @@
<template> <template>
<errors v-if="error" :errorCode="error.message" /> <errors v-if="error" :errorCode="error.status" />
<div class="row" v-else-if="!loading"> <div class="row" v-else-if="!loading">
<div class="column"> <div class="column">
<form @submit="save" class="card"> <form @submit="save" class="card">

View File

@ -1,5 +1,5 @@
<template> <template>
<errors v-if="error" :errorCode="error.message" /> <errors v-if="error" :errorCode="error.status" />
<div class="row" v-else-if="!loading"> <div class="row" v-else-if="!loading">
<div class="column"> <div class="column">
<div class="card"> <div class="card">

View File

@ -71,7 +71,7 @@ func withUser(fn handleFunc) handleFunc {
token, err := request.ParseFromRequest(r, &extractor{}, keyFunc, request.WithClaims(&tk)) token, err := request.ParseFromRequest(r, &extractor{}, keyFunc, request.WithClaims(&tk))
if err != nil || !token.Valid { if err != nil || !token.Valid {
return http.StatusForbidden, nil return http.StatusUnauthorized, nil
} }
expired := !tk.VerifyExpiresAt(time.Now().Add(time.Hour).Unix(), true) expired := !tk.VerifyExpiresAt(time.Now().Add(time.Hour).Unix(), true)

View File

@ -27,7 +27,7 @@ func NewHandler(
r := mux.NewRouter() r := mux.NewRouter()
r.Use(func(next http.Handler) http.Handler { r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", `default-src 'self'`) w.Header().Set("Content-Security-Policy", `default-src 'self'; style-src 'unsafe-inline';`)
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
}) })
}) })