mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
feat(desktop): sign every Windows release artifact (#1548)
This commit is contained in:
1 parent
873c203b5d
commit
6e66c92dca
89 files changed
+2408
-2740
No files matched your search
@@ -4,13 +4,7 @@ import {Readable} from 'node:stream';
|
||||
import {
|
||||
DesktopChecksumRedirectParam,
|
||||
DesktopRedirectParam,
|
||||
DesktopSourceChecksumResponse,
|
||||
DesktopTestBuildQuery,
|
||||
DesktopVariantChecksumRedirectParam,
|
||||
DesktopVariantRedirectParam,
|
||||
DesktopVariantVersionedChecksumRedirectParam,
|
||||
DesktopVariantVersionedRedirectParam,
|
||||
DesktopVariantVersionsParam,
|
||||
DesktopVersionedChecksumRedirectParam,
|
||||
DesktopVersionedRedirectParam,
|
||||
DesktopVersionsParam,
|
||||
@@ -136,239 +130,6 @@ function checksumFileResponse(ctx: Context<HonoEnv>, checksum: DesktopChecksumFi
|
||||
}
|
||||
|
||||
export function DownloadController(routes: Hono<HonoEnv>): void {
|
||||
routes.on(
|
||||
['GET', 'HEAD'],
|
||||
`${DESKTOP_REDIRECT_PREFIX}/source/latest`,
|
||||
OpenAPI({
|
||||
operationId: 'download_latest_desktop_source',
|
||||
summary: 'Download latest desktop source tarball',
|
||||
responseSchema: null,
|
||||
statusCode: 200,
|
||||
security: [],
|
||||
tags: ['Downloads'],
|
||||
description: 'Streams the latest published Fluxer desktop source tarball from the downloads bucket.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const downloadService = ctx.get('downloadService');
|
||||
const info = await downloadService.getLatestDesktopSourceInfo({baseUrl: Config.endpoints.apiClient});
|
||||
if (!info) {
|
||||
return ctx.text('Not Found', 404);
|
||||
}
|
||||
return streamArtifactResponse(ctx, downloadService, info.key, 'public, max-age=300', info.filename);
|
||||
},
|
||||
);
|
||||
routes.get(
|
||||
`${DESKTOP_REDIRECT_PREFIX}/source/latest/sha256`,
|
||||
OpenAPI({
|
||||
operationId: 'get_latest_desktop_source_sha256',
|
||||
summary: 'Get latest desktop source tarball checksum',
|
||||
responseSchema: DesktopSourceChecksumResponse,
|
||||
statusCode: 200,
|
||||
security: [],
|
||||
tags: ['Downloads'],
|
||||
description: 'Returns the SHA-256 checksum and metadata for the latest published Fluxer desktop source tarball.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const info = await ctx.get('downloadService').getLatestDesktopSourceInfo({baseUrl: Config.endpoints.apiClient});
|
||||
if (!info) {
|
||||
return ctx.text('Not Found', 404);
|
||||
}
|
||||
return ctx.json(
|
||||
{
|
||||
sha256: info.sha256,
|
||||
filename: info.filename,
|
||||
url: info.url,
|
||||
...(info.commit ? {commit: info.commit} : {}),
|
||||
...(info.desktop_version ? {desktop_version: info.desktop_version} : {}),
|
||||
...(info.desktop_version_source ? {desktop_version_source: info.desktop_version_source} : {}),
|
||||
published_at: info.published_at,
|
||||
...(info.size === undefined ? {} : {size: info.size}),
|
||||
},
|
||||
200,
|
||||
{'Cache-Control': 'public, max-age=300'},
|
||||
);
|
||||
},
|
||||
);
|
||||
routes.get(
|
||||
`${DESKTOP_REDIRECT_PREFIX}/:channel/:plat/:arch/:variant/latest`,
|
||||
Validator('param', DesktopVariantVersionsParam),
|
||||
Validator('query', DesktopTestBuildQuery),
|
||||
OpenAPI({
|
||||
operationId: 'get_latest_desktop_variant_version',
|
||||
summary: 'Get latest desktop variant version',
|
||||
responseSchema: VersionInfoResponse,
|
||||
statusCode: 200,
|
||||
security: [],
|
||||
tags: ['Downloads'],
|
||||
description:
|
||||
'Returns metadata for the latest desktop version in a build variant, including download URLs and SHA-256 checksums for all available formats.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const {channel, plat, arch, variant} = ctx.req.valid('param');
|
||||
const {test} = ctx.req.valid('query');
|
||||
const result = await ctx.get('downloadService').getLatestDesktopVersion({
|
||||
channel,
|
||||
plat,
|
||||
arch,
|
||||
variant,
|
||||
baseUrl: Config.endpoints.apiClient,
|
||||
test,
|
||||
});
|
||||
if (!result) {
|
||||
return ctx.text('Not Found', 404);
|
||||
}
|
||||
return ctx.json(result, 200, {
|
||||
'Cache-Control': 'public, max-age=300',
|
||||
});
|
||||
},
|
||||
);
|
||||
routes.on(
|
||||
['GET', 'HEAD'],
|
||||
`${DESKTOP_REDIRECT_PREFIX}/:channel/:plat/:arch/:variant/latest/:format{[a-z_]+\\.sha256}`,
|
||||
Validator('param', DesktopVariantChecksumRedirectParam),
|
||||
Validator('query', DesktopTestBuildQuery),
|
||||
OpenAPI({
|
||||
operationId: 'download_latest_desktop_variant_checksum',
|
||||
summary: 'Download latest desktop variant checksum',
|
||||
responseSchema: null,
|
||||
statusCode: 200,
|
||||
security: [],
|
||||
tags: ['Downloads'],
|
||||
description:
|
||||
'Returns a plain text SHA-256 checksum file for the latest available desktop application version in a build variant.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const {channel, plat, arch, variant, format} = ctx.req.valid('param');
|
||||
const {test} = ctx.req.valid('query');
|
||||
const checksum = await ctx
|
||||
.get('downloadService')
|
||||
.resolveLatestDesktopChecksumFile({channel, plat, arch, variant, format, test});
|
||||
if (!checksum) {
|
||||
return ctx.text('Not Found', 404);
|
||||
}
|
||||
return checksumFileResponse(ctx, checksum, 'no-store');
|
||||
},
|
||||
);
|
||||
routes.on(
|
||||
['GET', 'HEAD'],
|
||||
`${DESKTOP_REDIRECT_PREFIX}/:channel/:plat/:arch/:variant/latest/:format`,
|
||||
Validator('param', DesktopVariantRedirectParam),
|
||||
Validator('query', DesktopTestBuildQuery),
|
||||
OpenAPI({
|
||||
operationId: 'download_latest_desktop_variant',
|
||||
summary: 'Download latest desktop variant',
|
||||
responseSchema: null,
|
||||
statusCode: 200,
|
||||
security: [],
|
||||
tags: ['Downloads'],
|
||||
description:
|
||||
'Streams the latest available desktop application version for the specified platform, architecture, and build variant.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const {channel, plat, arch, variant, format} = ctx.req.valid('param');
|
||||
const {test} = ctx.req.valid('query');
|
||||
const downloadService = ctx.get('downloadService');
|
||||
const key = await downloadService.resolveLatestDesktopKey({channel, plat, arch, variant, format, test});
|
||||
if (!key) {
|
||||
return ctx.text('Not Found', 404);
|
||||
}
|
||||
return streamArtifactResponse(ctx, downloadService, key, 'no-store');
|
||||
},
|
||||
);
|
||||
routes.get(
|
||||
`${DESKTOP_REDIRECT_PREFIX}/:channel/:plat/:arch/:variant/versions`,
|
||||
Validator('param', DesktopVariantVersionsParam),
|
||||
Validator('query', DesktopVersionsQuery),
|
||||
OpenAPI({
|
||||
operationId: 'list_desktop_variant_versions',
|
||||
summary: 'List desktop variant versions',
|
||||
responseSchema: DesktopVersionsResponse,
|
||||
statusCode: 200,
|
||||
security: [],
|
||||
tags: ['Downloads'],
|
||||
description:
|
||||
'Lists available desktop versions with pagination for the specified platform, architecture, and variant.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const {channel, plat, arch, variant} = ctx.req.valid('param');
|
||||
const {limit, before, after, test} = ctx.req.valid('query');
|
||||
const {versions, hasMore} = await ctx.get('downloadService').listDesktopVersions({
|
||||
channel,
|
||||
plat,
|
||||
arch,
|
||||
variant,
|
||||
limit,
|
||||
before,
|
||||
after,
|
||||
baseUrl: Config.endpoints.apiClient,
|
||||
test,
|
||||
});
|
||||
return ctx.json({versions, has_more: hasMore}, 200, {
|
||||
'Cache-Control': 'public, max-age=300',
|
||||
});
|
||||
},
|
||||
);
|
||||
routes.on(
|
||||
['GET', 'HEAD'],
|
||||
`${DESKTOP_REDIRECT_PREFIX}/:channel/:plat/:arch/:variant/:version/:format{[a-z_]+\\.sha256}`,
|
||||
Validator('param', DesktopVariantVersionedChecksumRedirectParam),
|
||||
Validator('query', DesktopTestBuildQuery),
|
||||
OpenAPI({
|
||||
operationId: 'download_desktop_variant_version_checksum',
|
||||
summary: 'Download desktop variant version checksum',
|
||||
responseSchema: null,
|
||||
statusCode: 200,
|
||||
security: [],
|
||||
tags: ['Downloads'],
|
||||
description:
|
||||
'Returns a plain text SHA-256 checksum file for a specific desktop application version in a build variant.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const {channel, plat, arch, variant, version, format} = ctx.req.valid('param');
|
||||
const {test} = ctx.req.valid('query');
|
||||
const checksum = await ctx
|
||||
.get('downloadService')
|
||||
.resolveVersionedDesktopChecksumFile({channel, plat, arch, variant, version, format, test});
|
||||
if (!checksum) {
|
||||
return ctx.text('Not Found', 404);
|
||||
}
|
||||
return checksumFileResponse(ctx, checksum, 'public, max-age=86400');
|
||||
},
|
||||
);
|
||||
routes.on(
|
||||
['GET', 'HEAD'],
|
||||
`${DESKTOP_REDIRECT_PREFIX}/:channel/:plat/:arch/:variant/:version/:format`,
|
||||
Validator('param', DesktopVariantVersionedRedirectParam),
|
||||
Validator('query', DesktopTestBuildQuery),
|
||||
OpenAPI({
|
||||
operationId: 'download_desktop_variant_version',
|
||||
summary: 'Download desktop variant version',
|
||||
responseSchema: null,
|
||||
statusCode: 200,
|
||||
security: [],
|
||||
tags: ['Downloads'],
|
||||
description:
|
||||
'Streams a specific desktop application version for the given platform, architecture, and build variant.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const {channel, plat, arch, variant, version, format} = ctx.req.valid('param');
|
||||
const {test} = ctx.req.valid('query');
|
||||
const downloadService = ctx.get('downloadService');
|
||||
const key = await downloadService.resolveVersionedDesktopKey({
|
||||
channel,
|
||||
plat,
|
||||
arch,
|
||||
variant,
|
||||
version,
|
||||
format,
|
||||
test,
|
||||
});
|
||||
if (!key) {
|
||||
return ctx.text('Not Found', 404);
|
||||
}
|
||||
return streamArtifactResponse(ctx, downloadService, key, 'public, max-age=86400');
|
||||
},
|
||||
);
|
||||
routes.get(
|
||||
`${DESKTOP_REDIRECT_PREFIX}/:channel/:plat/:arch/latest`,
|
||||
Validator('param', DesktopVersionsParam),
|
||||
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
DesktopChannel,
|
||||
DesktopFormat,
|
||||
DesktopPlatform,
|
||||
DesktopVariant,
|
||||
} from '@fluxer/schema/src/domains/download/DownloadSchemas';
|
||||
import {Config} from '../Config';
|
||||
import type {IStorageService} from '../infrastructure/IStorageService';
|
||||
@@ -46,7 +45,6 @@ function isUnsatisfiableRangeError(error: unknown): boolean {
|
||||
}
|
||||
const DESKTOP_BUCKET_PREFIX = 'desktop';
|
||||
const DESKTOP_TEST_BUCKET_PREFIX = 'desktop-test';
|
||||
const DESKTOP_SOURCE_MANIFEST_KEY = `${DESKTOP_BUCKET_PREFIX}/source/latest.json`;
|
||||
const DEFAULT_API_CLIENT_BASE_URL = 'https://api.fluxer.app';
|
||||
|
||||
function desktopBucketPrefix(test?: boolean): string {
|
||||
@@ -57,14 +55,9 @@ function desktopArtifactPrefix(params: {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
test?: boolean;
|
||||
}): string | null {
|
||||
if (params.variant && params.plat !== 'win32') {
|
||||
return null;
|
||||
}
|
||||
const base = `${desktopBucketPrefix(params.test)}/${params.channel}/${params.plat}/${params.arch}`;
|
||||
return params.variant ? `${base}/${params.variant}` : base;
|
||||
return `${desktopBucketPrefix(params.test)}/${params.channel}/${params.plat}/${params.arch}`;
|
||||
}
|
||||
|
||||
type DesktopManifestFileEntry =
|
||||
@@ -77,7 +70,6 @@ type DesktopManifest = {
|
||||
channel: DesktopChannel;
|
||||
platform: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant | null;
|
||||
version: string;
|
||||
pub_date: string;
|
||||
minimum_system_version?: string | null;
|
||||
@@ -111,7 +103,6 @@ type VersionFile = {
|
||||
};
|
||||
type VersionInfo = {
|
||||
version: string;
|
||||
variant?: DesktopVariant | null;
|
||||
pub_date: string;
|
||||
minimum_system_version?: string | null;
|
||||
files: Record<string, VersionFile>;
|
||||
@@ -121,26 +112,6 @@ export type DesktopChecksumFile = {
|
||||
sha256: string;
|
||||
body: string;
|
||||
};
|
||||
type DesktopSourceManifest = {
|
||||
filename: string;
|
||||
key: string;
|
||||
sha256: string;
|
||||
commit?: string;
|
||||
desktop_version?: string;
|
||||
desktop_version_source?: {
|
||||
channel: DesktopChannel;
|
||||
platform: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
key: string;
|
||||
pub_date: string;
|
||||
};
|
||||
published_at: string;
|
||||
size?: number;
|
||||
};
|
||||
type DesktopSourceInfo = DesktopSourceManifest & {
|
||||
url: string;
|
||||
};
|
||||
|
||||
function isDesktopManifestFileEntry(value: unknown): value is DesktopManifestFileEntry {
|
||||
if (typeof value === 'string') {
|
||||
return true;
|
||||
@@ -154,7 +125,6 @@ function isDesktopManifest(value: unknown): value is DesktopManifest {
|
||||
(value.channel === 'stable' || value.channel === 'canary') &&
|
||||
(value.platform === 'win32' || value.platform === 'darwin' || value.platform === 'linux') &&
|
||||
(value.arch === 'x64' || value.arch === 'arm64') &&
|
||||
(value.variant === undefined || value.variant === null || value.variant === 'windows-game-capture') &&
|
||||
typeof value.version === 'string' &&
|
||||
typeof value.pub_date === 'string' &&
|
||||
(value.minimum_system_version === undefined ||
|
||||
@@ -164,37 +134,11 @@ function isDesktopManifest(value: unknown): value is DesktopManifest {
|
||||
);
|
||||
}
|
||||
|
||||
function isDesktopSourceManifest(value: unknown): value is DesktopSourceManifest {
|
||||
if (!isJsonRecord(value)) return false;
|
||||
return (
|
||||
typeof value.filename === 'string' &&
|
||||
typeof value.key === 'string' &&
|
||||
typeof value.sha256 === 'string' &&
|
||||
(value.commit === undefined || typeof value.commit === 'string') &&
|
||||
(value.desktop_version === undefined || typeof value.desktop_version === 'string') &&
|
||||
(value.desktop_version_source === undefined || isDesktopVersionSource(value.desktop_version_source)) &&
|
||||
typeof value.published_at === 'string' &&
|
||||
(value.size === undefined || typeof value.size === 'number')
|
||||
);
|
||||
}
|
||||
|
||||
function isDesktopVersionSource(value: unknown): value is DesktopSourceManifest['desktop_version_source'] {
|
||||
if (!isJsonRecord(value)) return false;
|
||||
return (
|
||||
(value.channel === 'stable' || value.channel === 'canary') &&
|
||||
(value.platform === 'win32' || value.platform === 'darwin' || value.platform === 'linux') &&
|
||||
(value.arch === 'x64' || value.arch === 'arm64') &&
|
||||
typeof value.key === 'string' &&
|
||||
typeof value.pub_date === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
interface LatestFilenameLookupParams {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
format: DesktopFormat;
|
||||
variant?: DesktopVariant;
|
||||
test?: boolean;
|
||||
}
|
||||
|
||||
@@ -205,42 +149,11 @@ interface ManifestFilenameResolutionParams extends LatestFilenameLookupParams {
|
||||
export class DownloadService {
|
||||
constructor(private readonly storageService: IStorageService) {}
|
||||
|
||||
async getLatestDesktopSourceInfo(params: {baseUrl?: string} = {}): Promise<DesktopSourceInfo | null> {
|
||||
const manifest = await this.readDesktopSourceManifest();
|
||||
if (!manifest) {
|
||||
return null;
|
||||
}
|
||||
let size = manifest.size;
|
||||
try {
|
||||
const metadata = await this.storageService.getObjectMetadata(Config.s3.buckets.downloads, manifest.key);
|
||||
if (!metadata) {
|
||||
return null;
|
||||
}
|
||||
size = size ?? metadata.contentLength;
|
||||
} catch (error) {
|
||||
if (error instanceof S3ServiceException && (error.name === 'NoSuchKey' || error.name === 'NotFound')) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
...manifest,
|
||||
...(size === undefined ? {} : {size}),
|
||||
url: `${this.buildBaseUrl(params.baseUrl)}${DESKTOP_REDIRECT_PREFIX}/source/latest`,
|
||||
};
|
||||
}
|
||||
|
||||
async resolveLatestDesktopSourceKey(): Promise<string | null> {
|
||||
const info = await this.getLatestDesktopSourceInfo();
|
||||
return info?.key ?? null;
|
||||
}
|
||||
|
||||
async resolveLatestDesktopKey(params: {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
format: DesktopFormat;
|
||||
variant?: DesktopVariant;
|
||||
test?: boolean;
|
||||
}): Promise<string | null> {
|
||||
const prefix = desktopArtifactPrefix(params);
|
||||
@@ -267,7 +180,6 @@ export class DownloadService {
|
||||
arch: params.arch,
|
||||
format: params.format,
|
||||
filename,
|
||||
variant: params.variant,
|
||||
test: params.test,
|
||||
});
|
||||
if (!resolvedFilename) {
|
||||
@@ -278,7 +190,6 @@ export class DownloadService {
|
||||
plat: params.plat,
|
||||
arch: params.arch,
|
||||
filename: resolvedFilename,
|
||||
variant: params.variant,
|
||||
test: params.test,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -293,7 +204,6 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
baseUrl?: string;
|
||||
test?: boolean;
|
||||
}): Promise<VersionInfo | null> {
|
||||
@@ -324,7 +234,6 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
limit: number;
|
||||
before?: string | null;
|
||||
after?: string | null;
|
||||
@@ -457,7 +366,6 @@ export class DownloadService {
|
||||
channel: params.channel,
|
||||
plat: params.plat,
|
||||
arch: params.arch,
|
||||
variant: params.variant,
|
||||
version,
|
||||
format,
|
||||
baseUrl: params.baseUrl,
|
||||
@@ -469,7 +377,6 @@ export class DownloadService {
|
||||
channel: params.channel,
|
||||
plat: params.plat,
|
||||
arch: params.arch,
|
||||
variant: params.variant,
|
||||
version,
|
||||
format,
|
||||
baseUrl: params.baseUrl,
|
||||
@@ -480,7 +387,6 @@ export class DownloadService {
|
||||
}
|
||||
versions.push({
|
||||
version,
|
||||
...(params.variant ? {variant: params.variant} : {}),
|
||||
pub_date: entry.pub_date.toISOString(),
|
||||
files,
|
||||
});
|
||||
@@ -498,7 +404,6 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
version: string;
|
||||
format: DesktopFormat;
|
||||
test?: boolean;
|
||||
@@ -544,7 +449,6 @@ export class DownloadService {
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
format: DesktopFormat;
|
||||
variant?: DesktopVariant;
|
||||
test?: boolean;
|
||||
}): Promise<DesktopChecksumFile | null> {
|
||||
const version = await this.getLatestDesktopVersion(params);
|
||||
@@ -564,7 +468,6 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
version: string;
|
||||
format: DesktopFormat;
|
||||
test?: boolean;
|
||||
@@ -592,11 +495,9 @@ export class DownloadService {
|
||||
return null;
|
||||
}
|
||||
const rewrittenKey = params.test ? this.rewriteToTestBucketKey(key) : key;
|
||||
const keysToTry = [rewrittenKey];
|
||||
const normalizedKey = this.normalizePlatformArchKey(rewrittenKey);
|
||||
if (normalizedKey) {
|
||||
keysToTry.push(normalizedKey);
|
||||
}
|
||||
const candidateKeys = [rewrittenKey, normalizedKey];
|
||||
const keysToTry = Array.from(new Set(candidateKeys.filter((candidate): candidate is string => candidate !== null)));
|
||||
for (const candidateKey of keysToTry) {
|
||||
try {
|
||||
const metadata = await this.storageService.getObjectMetadata(Config.s3.buckets.downloads, candidateKey);
|
||||
@@ -669,14 +570,12 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
version: string;
|
||||
format: DesktopFormat;
|
||||
baseUrl?: string;
|
||||
test?: boolean;
|
||||
}): string {
|
||||
const variantSegment = params.variant ? `/${params.variant}` : '';
|
||||
const url = `${this.buildBaseUrl(params.baseUrl)}${DOWNLOAD_PREFIX}/desktop/${params.channel}/${params.plat}/${params.arch}${variantSegment}/${params.version}/${params.format}`;
|
||||
const url = `${this.buildBaseUrl(params.baseUrl)}${DOWNLOAD_PREFIX}/desktop/${params.channel}/${params.plat}/${params.arch}/${params.version}/${params.format}`;
|
||||
return params.test ? `${url}?test=1` : url;
|
||||
}
|
||||
|
||||
@@ -684,14 +583,12 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
version: string;
|
||||
format: DesktopFormat;
|
||||
baseUrl?: string;
|
||||
test?: boolean;
|
||||
}): string {
|
||||
const variantSegment = params.variant ? `/${params.variant}` : '';
|
||||
const url = `${this.buildBaseUrl(params.baseUrl)}${DOWNLOAD_PREFIX}/desktop/${params.channel}/${params.plat}/${params.arch}${variantSegment}/${params.version}/${params.format}.sha256`;
|
||||
const url = `${this.buildBaseUrl(params.baseUrl)}${DOWNLOAD_PREFIX}/desktop/${params.channel}/${params.plat}/${params.arch}/${params.version}/${params.format}.sha256`;
|
||||
return params.test ? `${url}?test=1` : url;
|
||||
}
|
||||
|
||||
@@ -722,65 +619,10 @@ export class DownloadService {
|
||||
return parseJsonUnknown(text);
|
||||
}
|
||||
|
||||
private async readDesktopSourceManifest(): Promise<DesktopSourceManifest | null> {
|
||||
let manifest: unknown | null;
|
||||
try {
|
||||
manifest = await this.readJsonObjectFromStorage(DESKTOP_SOURCE_MANIFEST_KEY);
|
||||
} catch (error) {
|
||||
if (error instanceof S3ServiceException && (error.name === 'NoSuchKey' || error.name === 'NotFound')) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
if (!isDesktopSourceManifest(manifest)) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
!this.isSafeDesktopSourceKey(manifest.key) ||
|
||||
!this.isValidSha256(manifest.sha256) ||
|
||||
(manifest.desktop_version !== undefined && !this.isValidVersion(manifest.desktop_version)) ||
|
||||
(manifest.desktop_version_source !== undefined &&
|
||||
!this.isValidDesktopVersionSource(manifest.desktop_version_source)) ||
|
||||
manifest.filename.trim().length === 0 ||
|
||||
manifest.published_at.trim().length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private isSafeDesktopSourceKey(key: string): boolean {
|
||||
if (!key.startsWith(`${DESKTOP_BUCKET_PREFIX}/source/by-commit/`)) {
|
||||
return false;
|
||||
}
|
||||
const normalized = posix.normalize(key);
|
||||
if (normalized !== key || normalized.startsWith('..') || normalized.includes('\0')) {
|
||||
return false;
|
||||
}
|
||||
return key.endsWith('.tar.gz');
|
||||
}
|
||||
|
||||
private isValidSha256(value: string): boolean {
|
||||
return /^[a-f0-9]{64}$/u.test(value);
|
||||
}
|
||||
|
||||
private isValidVersion(value: string): boolean {
|
||||
return /^\d+\.\d+\.\d+$/u.test(value);
|
||||
}
|
||||
|
||||
private isValidDesktopVersionSource(source: DesktopSourceManifest['desktop_version_source']): boolean {
|
||||
if (!source) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
source.channel === 'canary' &&
|
||||
source.platform === 'linux' &&
|
||||
source.arch === 'x64' &&
|
||||
source.key === 'desktop/canary/linux/x64/manifest.json' &&
|
||||
source.pub_date.trim().length > 0
|
||||
);
|
||||
}
|
||||
|
||||
private async resolveManifestFilename(params: ManifestFilenameResolutionParams): Promise<string | null> {
|
||||
const manifestFilename = params.filename.trim();
|
||||
if (manifestFilename.length === 0) {
|
||||
@@ -792,7 +634,6 @@ export class DownloadService {
|
||||
channel: params.channel,
|
||||
plat: params.plat,
|
||||
arch: params.arch,
|
||||
variant: params.variant,
|
||||
filename: manifestFilename,
|
||||
test: params.test,
|
||||
}))
|
||||
@@ -1038,7 +879,6 @@ export class DownloadService {
|
||||
plat: params.plat,
|
||||
arch: params.arch,
|
||||
filename,
|
||||
variant: params.variant,
|
||||
test: params.test,
|
||||
});
|
||||
}
|
||||
@@ -1047,7 +887,6 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
version: string;
|
||||
format: DesktopFormat;
|
||||
test?: boolean;
|
||||
@@ -1079,7 +918,6 @@ export class DownloadService {
|
||||
arch: params.arch,
|
||||
format: params.format,
|
||||
filename,
|
||||
variant: params.variant,
|
||||
test: params.test,
|
||||
});
|
||||
if (!resolvedFilename) {
|
||||
@@ -1090,7 +928,6 @@ export class DownloadService {
|
||||
plat: params.plat,
|
||||
arch: params.arch,
|
||||
filename: resolvedFilename,
|
||||
variant: params.variant,
|
||||
test: params.test,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -1106,7 +943,6 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
baseUrl?: string;
|
||||
test?: boolean;
|
||||
},
|
||||
@@ -1125,7 +961,6 @@ export class DownloadService {
|
||||
arch: params.arch,
|
||||
format,
|
||||
filename: manifestFilename,
|
||||
variant: params.variant,
|
||||
test: params.test,
|
||||
});
|
||||
if (!resolvedFilename) {
|
||||
@@ -1143,7 +978,6 @@ export class DownloadService {
|
||||
arch: params.arch,
|
||||
format,
|
||||
filename: resolvedFilename,
|
||||
variant: params.variant,
|
||||
test: params.test,
|
||||
})
|
||||
) {
|
||||
@@ -1156,7 +990,6 @@ export class DownloadService {
|
||||
entry,
|
||||
manifestFilename,
|
||||
resolvedFilename,
|
||||
variant: params.variant,
|
||||
test: params.test,
|
||||
});
|
||||
files[format] = {
|
||||
@@ -1164,7 +997,6 @@ export class DownloadService {
|
||||
channel: params.channel,
|
||||
plat: params.plat,
|
||||
arch: params.arch,
|
||||
variant: params.variant,
|
||||
version: manifest.version,
|
||||
format,
|
||||
baseUrl: params.baseUrl,
|
||||
@@ -1176,7 +1008,6 @@ export class DownloadService {
|
||||
channel: params.channel,
|
||||
plat: params.plat,
|
||||
arch: params.arch,
|
||||
variant: params.variant,
|
||||
version: manifest.version,
|
||||
format,
|
||||
baseUrl: params.baseUrl,
|
||||
@@ -1191,7 +1022,6 @@ export class DownloadService {
|
||||
const minimumSystemVersion = manifest.minimum_system_version ?? null;
|
||||
return {
|
||||
version: manifest.version,
|
||||
...(params.variant ? {variant: params.variant} : {}),
|
||||
pub_date: manifest.pub_date,
|
||||
...(minimumSystemVersion ? {minimum_system_version: minimumSystemVersion} : {}),
|
||||
files,
|
||||
@@ -1202,7 +1032,6 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
baseUrl?: string;
|
||||
test?: boolean;
|
||||
}): Promise<VersionInfo | null> {
|
||||
@@ -1210,7 +1039,6 @@ export class DownloadService {
|
||||
channel: params.channel,
|
||||
plat: params.plat,
|
||||
arch: params.arch,
|
||||
variant: params.variant,
|
||||
limit: 1,
|
||||
baseUrl: params.baseUrl,
|
||||
test: params.test,
|
||||
@@ -1222,7 +1050,6 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
entry: DesktopManifestFileEntry;
|
||||
manifestFilename: string;
|
||||
resolvedFilename: string;
|
||||
@@ -1238,7 +1065,6 @@ export class DownloadService {
|
||||
channel: params.channel,
|
||||
plat: params.plat,
|
||||
arch: params.arch,
|
||||
variant: params.variant,
|
||||
filename: params.resolvedFilename,
|
||||
test: params.test,
|
||||
});
|
||||
@@ -1270,7 +1096,6 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
filename: string;
|
||||
test?: boolean;
|
||||
}): Promise<boolean> {
|
||||
@@ -1293,7 +1118,6 @@ export class DownloadService {
|
||||
channel: DesktopChannel;
|
||||
plat: DesktopPlatform;
|
||||
arch: DesktopArch;
|
||||
variant?: DesktopVariant;
|
||||
filename: string;
|
||||
test?: boolean;
|
||||
}): string | null {
|
||||
|
||||
Reference in new issue
Block a user