mirror of
https://github.com/diced/zipline.git
synced 2025-12-15 17:11:35 -08:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
633dfd4712 | ||
|
|
e6ed7a36d5 | ||
|
|
93cb9eec4c | ||
|
|
4849cd8221 | ||
|
|
89c58044a3 | ||
|
|
40fb11256f | ||
|
|
d112c3a509 | ||
|
|
23af36563f | ||
|
|
28db15eb77 | ||
|
|
e9054bd3e5 | ||
|
|
713f857e28 | ||
|
|
5d6768029f | ||
|
|
72e24a8b86 | ||
|
|
86c3e780d1 | ||
|
|
5102620953 | ||
|
|
4d728f9f8b | ||
|
|
faf5098357 | ||
|
|
c4066fc851 |
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -7,5 +7,5 @@ contact_links:
|
||||
url: https://discord.gg/EAhCRfGxCF
|
||||
about: Ask for help with anything related to Zipline!
|
||||
- name: Zipline Docs
|
||||
url: https://zipline.diced.tech
|
||||
url: https://zipline.diced.sh
|
||||
about: Maybe take a look a the docs?
|
||||
|
||||
0
.yarn/releases/yarn-3.3.1.cjs
vendored
Executable file → Normal file
0
.yarn/releases/yarn-3.3.1.cjs
vendored
Executable file → Normal file
12
Dockerfile
12
Dockerfile
@@ -26,10 +26,6 @@ ENV PRISMA_QUERY_ENGINE_BINARY=/prisma-engines/query-engine \
|
||||
ZIPLINE_DOCKER_BUILD=true \
|
||||
NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Install production dependencies then temporarily save
|
||||
RUN yarn workspaces focus --production --all
|
||||
RUN cp -RL node_modules /tmp/node_modules
|
||||
|
||||
# Install the dependencies
|
||||
RUN yarn install --immutable
|
||||
|
||||
@@ -63,20 +59,20 @@ ENV PRISMA_QUERY_ENGINE_BINARY=/prisma-engines/query-engine \
|
||||
# Copy only the necessary files from the previous stage
|
||||
COPY --from=builder /zipline/dist ./dist
|
||||
COPY --from=builder /zipline/.next ./.next
|
||||
COPY --from=builder /zipline/package.json ./package.json
|
||||
|
||||
COPY --from=builder /zipline/mimes.json ./mimes.json
|
||||
COPY --from=builder /zipline/next.config.js ./next.config.js
|
||||
COPY --from=builder /zipline/public ./public
|
||||
|
||||
COPY --from=builder /zipline/node_modules ./node_modules
|
||||
|
||||
|
||||
# Copy Startup Script
|
||||
COPY docker-entrypoint.sh /zipline
|
||||
|
||||
# Make Startup Script Executable
|
||||
RUN chmod a+x /zipline/docker-entrypoint.sh && rm -rf /zipline/src
|
||||
|
||||
# Clean up
|
||||
RUN rm -rf /tmp/* /root/*
|
||||
RUN yarn cache clean --all
|
||||
|
||||
# Set the entrypoint to the startup script
|
||||
ENTRYPOINT ["tini", "--", "/zipline/docker-entrypoint.sh"]
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zipline",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.5",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "npm-run-all build:server dev:run",
|
||||
|
||||
@@ -1498,4 +1498,4 @@ wheat
|
||||
white
|
||||
whitesmoke
|
||||
yellow
|
||||
yellowgreen
|
||||
yellowgreen
|
||||
@@ -1747,4 +1747,4 @@ zigzagsalamander
|
||||
zonetailedpigeon
|
||||
zooplankton
|
||||
zopilote
|
||||
zorilla
|
||||
zorilla
|
||||
@@ -360,6 +360,11 @@ export default function Layout({ children, props }) {
|
||||
compact
|
||||
size='xl'
|
||||
p='sm'
|
||||
styles={{
|
||||
label: {
|
||||
overflow: 'unset',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{user.username}
|
||||
</Button>
|
||||
|
||||
@@ -367,8 +367,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
|
||||
<Title>Manage User</Title>
|
||||
<MutedText size='md'>
|
||||
Want to use variables in embed text? Visit{' '}
|
||||
<AnchorNext href='https://zipline.diced.tech/docs/guides/variables'>the docs</AnchorNext> for
|
||||
variables
|
||||
<AnchorNext href='https://zipline.diced.sh/docs/guides/variables'>the docs</AnchorNext> for variables
|
||||
</MutedText>
|
||||
|
||||
<TextInput
|
||||
|
||||
@@ -233,7 +233,7 @@ export default function File({ chunks: chunks_config }) {
|
||||
if (chunks_config.enabled && file.size >= chunks_config.max_size) {
|
||||
toChunkFiles.push(file);
|
||||
} else {
|
||||
body.append('file', files[i]);
|
||||
body.append('file', files[i], encodeURIComponent(files[i].name));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ export interface ConfigUploader {
|
||||
format_date: string;
|
||||
default_expiration: string;
|
||||
assume_mimetypes: boolean;
|
||||
random_words_separator: string;
|
||||
}
|
||||
|
||||
export interface ConfigUrls {
|
||||
@@ -135,9 +136,12 @@ export interface ConfigOAuth {
|
||||
|
||||
discord_client_id?: string;
|
||||
discord_client_secret?: string;
|
||||
discord_redirect_uri?: string;
|
||||
discord_whitelisted_users?: string[];
|
||||
|
||||
google_client_id?: string;
|
||||
google_client_secret?: string;
|
||||
google_redirect_uri?: string;
|
||||
}
|
||||
|
||||
export interface ConfigChunks {
|
||||
|
||||
@@ -98,6 +98,7 @@ export default function readConfig() {
|
||||
map('UPLOADER_FORMAT_DATE', 'string', 'uploader.format_date'),
|
||||
map('UPLOADER_DEFAULT_EXPIRATION', 'string', 'uploader.default_expiration'),
|
||||
map('UPLOADER_ASSUME_MIMETYPES', 'boolean', 'uploader.assume_mimetypes'),
|
||||
map('UPLOADER_RANDOM_WORDS_SEPARATOR', 'string', 'uploader.random_words_separator'),
|
||||
|
||||
map('URLS_ROUTE', 'string', 'urls.route'),
|
||||
map('URLS_LENGTH', 'number', 'urls.length'),
|
||||
@@ -146,9 +147,12 @@ export default function readConfig() {
|
||||
|
||||
map('OAUTH_DISCORD_CLIENT_ID', 'string', 'oauth.discord_client_id'),
|
||||
map('OAUTH_DISCORD_CLIENT_SECRET', 'string', 'oauth.discord_client_secret'),
|
||||
map('OAUTH_DISCORD_REDIRECT_URI', 'string', 'oauth.discord_redirect_uri'),
|
||||
map('OAUTH_DISCORD_WHITELISTED_USERS', 'array', 'oauth.discord_whitelisted_users'),
|
||||
|
||||
map('OAUTH_GOOGLE_CLIENT_ID', 'string', 'oauth.google_client_id'),
|
||||
map('OAUTH_GOOGLE_CLIENT_SECRET', 'string', 'oauth.google_client_secret'),
|
||||
map('OAUTH_GOOGLE_REDIRECT_URI', 'string', 'oauth.google_redirect_uri'),
|
||||
|
||||
map('FEATURES_INVITES', 'boolean', 'features.invites'),
|
||||
map('FEATURES_INVITES_LENGTH', 'number', 'features.invites_length'),
|
||||
|
||||
@@ -97,6 +97,7 @@ const validator = s.object({
|
||||
format_date: s.string.default('YYYY-MM-DD_HH:mm:ss'),
|
||||
default_expiration: s.string.optional.default(null),
|
||||
assume_mimetypes: s.boolean.default(false),
|
||||
random_words_separator: s.string.default('-'),
|
||||
})
|
||||
.default({
|
||||
default_format: 'RANDOM',
|
||||
@@ -144,7 +145,7 @@ const validator = s.object({
|
||||
)
|
||||
.default([
|
||||
{ label: 'Zipline', link: 'https://github.com/diced/zipline' },
|
||||
{ label: 'Documentation', link: 'https://zipline.diced.tech/' },
|
||||
{ label: 'Documentation', link: 'https://zipline.diced.sh/' },
|
||||
]),
|
||||
})
|
||||
.default({
|
||||
@@ -155,7 +156,7 @@ const validator = s.object({
|
||||
|
||||
external_links: [
|
||||
{ label: 'Zipline', link: 'https://github.com/diced/zipline' },
|
||||
{ label: 'Documentation', link: 'https://zipline.diced.tech/' },
|
||||
{ label: 'Documentation', link: 'https://zipline.diced.sh/' },
|
||||
],
|
||||
}),
|
||||
discord: s
|
||||
@@ -176,9 +177,12 @@ const validator = s.object({
|
||||
|
||||
discord_client_id: s.string.nullable.default(null),
|
||||
discord_client_secret: s.string.nullable.default(null),
|
||||
discord_redirect_uri: s.string.nullable.default(null),
|
||||
discord_whitelisted_users: s.string.array.default([]),
|
||||
|
||||
google_client_id: s.string.nullable.default(null),
|
||||
google_client_secret: s.string.nullable.default(null),
|
||||
google_redirect_uri: s.string.nullable.default(null),
|
||||
})
|
||||
.nullish.default(null),
|
||||
features: s
|
||||
|
||||
@@ -1,26 +1,41 @@
|
||||
import { readFile } from 'fs/promises';
|
||||
import config from 'lib/config';
|
||||
import Logger from 'lib/logger';
|
||||
|
||||
const logger = Logger.get('random_words');
|
||||
|
||||
export type GfyCatWords = {
|
||||
adjectives: string[];
|
||||
animals: string[];
|
||||
};
|
||||
|
||||
export async function importWords(): Promise<GfyCatWords> {
|
||||
const adjectives = (await readFile('public/adjectives.txt', 'utf-8')).split('\n');
|
||||
const animals = (await readFile('public/animals.txt', 'utf-8')).split('\n');
|
||||
export async function importWords(): Promise<GfyCatWords | null> {
|
||||
try {
|
||||
const adjectives = (await readFile('public/adjectives.txt', 'utf-8')).split('\n').map((x) => x.trim());
|
||||
const animals = (await readFile('public/animals.txt', 'utf-8')).split('\n').map((x) => x.trim());
|
||||
|
||||
return {
|
||||
adjectives,
|
||||
animals,
|
||||
};
|
||||
return {
|
||||
adjectives,
|
||||
animals,
|
||||
};
|
||||
} catch {
|
||||
logger.error('public/adjectives.txt or public/animals.txt do not exist, to fix this please retrieve.');
|
||||
logger.error('to prevent this from happening again, remember to not delete your public/ directory.');
|
||||
logger.error('file names will use the RANDOM format instead until fixed');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function randomWord(words: string[]) {
|
||||
return words[Math.floor(Math.random() * words.length)];
|
||||
}
|
||||
|
||||
export default async function gfycat() {
|
||||
export default async function gfycat(): Promise<string | null> {
|
||||
const words = await importWords();
|
||||
|
||||
return `${randomWord(words.adjectives)}${randomWord(words.adjectives)}${randomWord(words.animals)}`;
|
||||
if (!words) return null;
|
||||
|
||||
return `${randomWord(words.adjectives)}${config.uploader.random_words_separator}${randomWord(
|
||||
words.adjectives
|
||||
)}${config.uploader.random_words_separator}${randomWord(words.animals)}`;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export default async function formatFileName(nameFormat: NameFormat, originalNam
|
||||
|
||||
return name;
|
||||
case 'gfycat':
|
||||
return gfycat();
|
||||
return gfycat() ?? random();
|
||||
default:
|
||||
return random();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import config from 'lib/config';
|
||||
import { notNull } from 'lib/util';
|
||||
import { isNotNullOrUndefined } from 'lib/util';
|
||||
import { GetServerSideProps } from 'next';
|
||||
|
||||
export type OauthProvider = {
|
||||
@@ -27,9 +27,15 @@ export type ServerSideProps = {
|
||||
};
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<ServerSideProps> = async (ctx) => {
|
||||
const ghEnabled = notNull(config.oauth?.github_client_id, config.oauth?.github_client_secret);
|
||||
const discEnabled = notNull(config.oauth?.discord_client_id, config.oauth?.discord_client_secret);
|
||||
const googleEnabled = notNull(config.oauth?.google_client_id, config.oauth?.google_client_secret);
|
||||
const ghEnabled =
|
||||
isNotNullOrUndefined(config.oauth?.github_client_id) &&
|
||||
isNotNullOrUndefined(config.oauth?.github_client_secret);
|
||||
const discEnabled =
|
||||
isNotNullOrUndefined(config.oauth?.discord_client_id) &&
|
||||
isNotNullOrUndefined(config.oauth?.discord_client_secret);
|
||||
const googleEnabled =
|
||||
isNotNullOrUndefined(config.oauth?.google_client_id) &&
|
||||
isNotNullOrUndefined(config.oauth?.google_client_secret);
|
||||
|
||||
const oauth_providers: OauthProvider[] = [];
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ export const github_auth = {
|
||||
};
|
||||
|
||||
export const discord_auth = {
|
||||
oauth_url: (clientId: string, origin: string, state?: string) =>
|
||||
oauth_url: (clientId: string, origin: string, state?: string, redirect_uri?: string) =>
|
||||
`https://discord.com/api/oauth2/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(
|
||||
`${origin}/api/auth/oauth/discord`
|
||||
redirect_uri || `${origin}/api/auth/oauth/discord`
|
||||
)}&response_type=code&scope=identify${state ? `&state=${state}` : ''}`,
|
||||
oauth_user: async (access_token: string) => {
|
||||
const res = await fetch('https://discord.com/api/users/@me', {
|
||||
@@ -33,9 +33,9 @@ export const discord_auth = {
|
||||
};
|
||||
|
||||
export const google_auth = {
|
||||
oauth_url: (clientId: string, origin: string, state?: string) =>
|
||||
oauth_url: (clientId: string, origin: string, state?: string, redirect_uri?: string) =>
|
||||
`https://accounts.google.com/o/oauth2/auth?client_id=${clientId}&redirect_uri=${encodeURIComponent(
|
||||
`${origin}/api/auth/oauth/google`
|
||||
redirect_uri || `${origin}/api/auth/oauth/google`
|
||||
)}&response_type=code&access_type=offline&scope=https://www.googleapis.com/auth/userinfo.profile${
|
||||
state ? `&state=${state}` : ''
|
||||
}`,
|
||||
|
||||
@@ -15,10 +15,12 @@ export const useVersion = () => {
|
||||
return useQuery<VersionResponse>(
|
||||
['version'],
|
||||
async () => {
|
||||
return fetch('/api/version').then((res) => res.json());
|
||||
return fetch('/api/version').then((res) => (res.ok ? res.json() : Promise.reject('')));
|
||||
},
|
||||
{
|
||||
staleTime: Infinity,
|
||||
refetchInterval: false,
|
||||
refetchOnMount: false,
|
||||
retry: false,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -99,7 +99,7 @@ export const createSpotlightActions = (router: NextRouter): SpotlightAction[] =>
|
||||
});
|
||||
}),
|
||||
|
||||
actionLink('Help', 'Documentation', 'View the documentation', 'https://zipline.diced.tech', <IconHelp />),
|
||||
actionLink('Help', 'Documentation', 'View the documentation', 'https://zipline.diced.sh', <IconHelp />),
|
||||
|
||||
// the list of actions here is very incomplete, and will be expanded in the future
|
||||
];
|
||||
|
||||
@@ -120,6 +120,6 @@ export async function getBase64URLFromURL(url: string) {
|
||||
return `data:${res.headers.get('content-type')};base64,${base64}`;
|
||||
}
|
||||
|
||||
export function notNull(a: unknown, b: unknown) {
|
||||
return a !== null && b !== null;
|
||||
export function isNotNullOrUndefined(value: unknown) {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import Logger from 'lib/logger';
|
||||
import { OAuthQuery, OAuthResponse, withOAuth } from 'lib/middleware/withOAuth';
|
||||
import { withZipline } from 'lib/middleware/withZipline';
|
||||
import { discord_auth } from 'lib/oauth';
|
||||
import { getBase64URLFromURL, notNull } from 'lib/util';
|
||||
import { getBase64URLFromURL, isNotNullOrUndefined } from 'lib/util';
|
||||
|
||||
async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promise<OAuthResponse> {
|
||||
if (!config.features.oauth_registration)
|
||||
@@ -12,7 +12,10 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
error: 'oauth registration is disabled',
|
||||
};
|
||||
|
||||
if (!notNull(config.oauth.discord_client_id, config.oauth.discord_client_secret)) {
|
||||
if (
|
||||
!isNotNullOrUndefined(config.oauth.discord_client_id) &&
|
||||
!isNotNullOrUndefined(config.oauth.discord_client_secret)
|
||||
) {
|
||||
logger.error('Discord OAuth is not configured');
|
||||
|
||||
return {
|
||||
@@ -26,7 +29,8 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
redirect: discord_auth.oauth_url(
|
||||
config.oauth.discord_client_id,
|
||||
`${config.core.return_https ? 'https' : 'http'}://${host}`,
|
||||
state
|
||||
state,
|
||||
config.oauth.discord_redirect_uri
|
||||
),
|
||||
};
|
||||
|
||||
@@ -35,7 +39,9 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
client_secret: config.oauth.discord_client_secret,
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: `${config.core.return_https ? 'https' : 'http'}://${host}/api/auth/oauth/discord`,
|
||||
redirect_uri:
|
||||
config.oauth.discord_redirect_uri ||
|
||||
`${config.core.return_https ? 'https' : 'http'}://${host}/api/auth/oauth/discord`,
|
||||
scope: 'identify',
|
||||
});
|
||||
|
||||
@@ -67,6 +73,12 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
: `https://cdn.discordapp.com/embed/avatars/${userJson.discriminator % 5}.png`;
|
||||
const avatarBase64 = await getBase64URLFromURL(avatar);
|
||||
|
||||
if (
|
||||
config.oauth.discord_whitelisted_users?.length &&
|
||||
!config.oauth.discord_whitelisted_users.includes(userJson.id)
|
||||
)
|
||||
return { error: 'user is not whitelisted' };
|
||||
|
||||
return {
|
||||
username: userJson.username,
|
||||
user_id: userJson.id,
|
||||
|
||||
@@ -3,7 +3,7 @@ import Logger from 'lib/logger';
|
||||
import { OAuthQuery, OAuthResponse, withOAuth } from 'lib/middleware/withOAuth';
|
||||
import { withZipline } from 'lib/middleware/withZipline';
|
||||
import { github_auth } from 'lib/oauth';
|
||||
import { getBase64URLFromURL, notNull } from 'lib/util';
|
||||
import { getBase64URLFromURL, isNotNullOrUndefined } from 'lib/util';
|
||||
|
||||
async function handler({ code, state }: OAuthQuery, logger: Logger): Promise<OAuthResponse> {
|
||||
if (!config.features.oauth_registration)
|
||||
@@ -12,7 +12,10 @@ async function handler({ code, state }: OAuthQuery, logger: Logger): Promise<OAu
|
||||
error: 'oauth registration is disabled',
|
||||
};
|
||||
|
||||
if (!notNull(config.oauth.github_client_id, config.oauth.github_client_secret)) {
|
||||
if (
|
||||
!isNotNullOrUndefined(config.oauth.github_client_id) &&
|
||||
!isNotNullOrUndefined(config.oauth.github_client_secret)
|
||||
) {
|
||||
logger.error('GitHub OAuth is not configured');
|
||||
return {
|
||||
error_code: 401,
|
||||
|
||||
@@ -3,7 +3,7 @@ import Logger from 'lib/logger';
|
||||
import { OAuthQuery, OAuthResponse, withOAuth } from 'lib/middleware/withOAuth';
|
||||
import { withZipline } from 'lib/middleware/withZipline';
|
||||
import { google_auth } from 'lib/oauth';
|
||||
import { getBase64URLFromURL, notNull } from 'lib/util';
|
||||
import { getBase64URLFromURL, isNotNullOrUndefined } from 'lib/util';
|
||||
|
||||
async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promise<OAuthResponse> {
|
||||
if (!config.features.oauth_registration)
|
||||
@@ -12,7 +12,10 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
error: 'oauth registration is disabled',
|
||||
};
|
||||
|
||||
if (!notNull(config.oauth.google_client_id, config.oauth.google_client_secret)) {
|
||||
if (
|
||||
!isNotNullOrUndefined(config.oauth.google_client_id) &&
|
||||
!isNotNullOrUndefined(config.oauth.google_client_secret)
|
||||
) {
|
||||
logger.error('Google OAuth is not configured');
|
||||
return {
|
||||
error_code: 401,
|
||||
@@ -25,7 +28,8 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
redirect: google_auth.oauth_url(
|
||||
config.oauth.google_client_id,
|
||||
`${config.core.return_https ? 'https' : 'http'}://${host}`,
|
||||
state
|
||||
state,
|
||||
config.oauth.google_redirect_uri
|
||||
),
|
||||
};
|
||||
|
||||
@@ -33,7 +37,9 @@ async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promi
|
||||
code,
|
||||
client_id: config.oauth.google_client_id,
|
||||
client_secret: config.oauth.google_client_secret,
|
||||
redirect_uri: `${config.core.return_https ? 'https' : 'http'}://${host}/api/auth/oauth/google`,
|
||||
redirect_uri:
|
||||
config.oauth.google_redirect_uri ||
|
||||
`${config.core.return_https ? 'https' : 'http'}://${host}/api/auth/oauth/google`,
|
||||
grant_type: 'authorization_code',
|
||||
});
|
||||
|
||||
|
||||
@@ -80,6 +80,16 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
|
||||
// handle partial uploads before ratelimits
|
||||
if (req.headers['content-range'] && zconfig.chunks.enabled) {
|
||||
if (format === 'name') {
|
||||
const existing = await prisma.file.findFirst({
|
||||
where: {
|
||||
name: req.headers['x-zipline-partial-filename'] as string,
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) return res.badRequest('filename already exists (conflict: NAME format)');
|
||||
}
|
||||
|
||||
// parses content-range header (bytes start-end/total)
|
||||
const [start, end, total] = req.headers['content-range']
|
||||
.replace('bytes ', '')
|
||||
@@ -199,17 +209,20 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
|
||||
for (let i = 0; i !== req.files.length; ++i) {
|
||||
const file = req.files[i];
|
||||
|
||||
if (file.size > zconfig.uploader[user.administrator ? 'admin_limit' : 'user_limit'])
|
||||
return res.badRequest(`file[${i}]: size too big`);
|
||||
if (!file.originalname) return res.badRequest(`file[${i}]: no filename`);
|
||||
|
||||
const ext = file.originalname.split('.').length === 1 ? '' : file.originalname.split('.').pop();
|
||||
const decodedName = decodeURI(file.originalname);
|
||||
|
||||
const ext = decodedName.split('.').length === 1 ? '' : decodedName.split('.').pop();
|
||||
if (zconfig.uploader.disabled_extensions.includes(ext))
|
||||
return res.badRequest(`file[${i}]: disabled extension recieved: ${ext}`);
|
||||
let fileName = await formatFileName(format, file.originalname);
|
||||
let fileName = await formatFileName(format, decodedName);
|
||||
|
||||
if (req.headers['x-zipline-filename']) {
|
||||
fileName = req.headers['x-zipline-filename'] as string;
|
||||
if (format === 'name' || req.headers['x-zipline-filename']) {
|
||||
fileName = (req.headers['x-zipline-filename'] as string) || fileName;
|
||||
const existing = await prisma.file.findFirst({
|
||||
where: {
|
||||
name: fileName,
|
||||
@@ -226,7 +239,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
let mimetype = file.mimetype;
|
||||
|
||||
if (file.mimetype === 'application/octet-stream' && zconfig.uploader.assume_mimetypes) {
|
||||
const ext = parse(file.originalname).ext.replace('.', '');
|
||||
const ext = parse(decodedName).ext.replace('.', '');
|
||||
const mime = await guess(ext);
|
||||
|
||||
if (!mime) response.assumed_mimetype = false;
|
||||
@@ -247,7 +260,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
password,
|
||||
expiresAt: expiry,
|
||||
maxViews: fileMaxViews,
|
||||
originalName: req.headers['original-name'] ? file.originalname ?? null : null,
|
||||
originalName: req.headers['original-name'] ? decodedName ?? null : null,
|
||||
size: file.size,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ async function handler(_: NextApiReq, res: NextApiRes) {
|
||||
|
||||
const pkg = JSON.parse(await readFile('package.json', 'utf8'));
|
||||
|
||||
const re = await fetch('https://zipline.diced.tech/api/version?c=' + pkg.version);
|
||||
const re = await fetch('https://zipline.diced.sh/api/version?c=' + pkg.version);
|
||||
const json = await re.json();
|
||||
|
||||
let updateToType = 'stable';
|
||||
|
||||
@@ -12,6 +12,7 @@ type LimitedFolder = {
|
||||
createdAt: Date | string;
|
||||
mimetype: string;
|
||||
views: number;
|
||||
size: number;
|
||||
}[];
|
||||
user: {
|
||||
username: string;
|
||||
@@ -83,6 +84,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
|
||||
views: true,
|
||||
createdAt: true,
|
||||
password: true,
|
||||
size: true,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
|
||||
@@ -128,6 +128,7 @@ export default function EmbeddedFile({
|
||||
</>
|
||||
)}
|
||||
|
||||
<meta property='og:type' content={'video.other'} />
|
||||
<meta property='og:url' content={`${host}/r/${file.name}`} />
|
||||
<meta property='og:video' content={`${host}/r/${file.name}`} />
|
||||
<meta property='og:video:url' content={`${host}/r/${file.name}`} />
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { readdir, readFile } from 'fs/promises';
|
||||
import { statSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import config from 'lib/config';
|
||||
import datasource from 'lib/datasource';
|
||||
import { guess } from 'lib/mimes';
|
||||
import { migrations } from 'server/util';
|
||||
import { bytesToHuman } from 'lib/utils/bytes';
|
||||
|
||||
async function main() {
|
||||
const directory = process.argv[2];
|
||||
@@ -25,13 +27,16 @@ async function main() {
|
||||
|
||||
for (let i = 0; i !== files.length; ++i) {
|
||||
const mime = await guess(files[i].split('.').pop());
|
||||
const { size } = statSync(join(directory, files[i]));
|
||||
|
||||
data.push({
|
||||
name: files[i],
|
||||
mimetype: mime,
|
||||
userId,
|
||||
size,
|
||||
});
|
||||
console.log(`Imported ${files[i]} (${mime} mimetype) to user ${userId}`);
|
||||
|
||||
console.log(`Imported ${files[i]} (${bytesToHuman(size)}) (${mime} mimetype) to user ${userId}`);
|
||||
}
|
||||
|
||||
process.env.DATABASE_URL = config.core.database_url;
|
||||
|
||||
@@ -20,7 +20,7 @@ function dbFileDecorator(fastify: FastifyInstance, _, done) {
|
||||
|
||||
this.header('Content-Length', size);
|
||||
this.header('Content-Type', download ? 'application/octet-stream' : file.mimetype);
|
||||
this.header('Content-Disposition', `inline; filename="${file.originalName || file.name}"`);
|
||||
this.header('Content-Disposition', `inline; filename="${encodeURI(file.originalName || file.name)}"`);
|
||||
|
||||
return this.send(data);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default async function uploadsRoute(this: FastifyInstance, req: FastifyRe
|
||||
|
||||
const image = await this.prisma.file.findFirst({
|
||||
where: {
|
||||
OR: [{ name: id }, { invisible: { invis: decodeURI(encodeURI(id)) } }],
|
||||
OR: [{ name: id }, { name: decodeURI(id) }, { invisible: { invis: decodeURI(encodeURI(id)) } }],
|
||||
},
|
||||
});
|
||||
if (!image) return reply.rawFile(id);
|
||||
|
||||
Reference in New Issue
Block a user