feat: import Chinese-localized Buzz source snapshot
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled

Signed-off-by: cls_宁波本机 <908705107@qq.com>
This commit is contained in:
2026-08-13 18:34:25 +08:00
parent 61c3fa1df9
commit 9dfa06ffee
3785 changed files with 1085458 additions and 2 deletions
@@ -0,0 +1,443 @@
import 'dart:convert';
import 'dart:typed_data';
const _pngSignature = <int>[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
const _allowedPngAncillaryChunks = {
'cHRM',
'gAMA',
'sBIT',
'sRGB',
'bKGD',
'hIST',
'tRNS',
'sPLT',
'acTL',
'fcTL',
'fdAT',
};
const _allowedWebpChunks = {'VP8 ', 'VP8L', 'VP8X', 'ALPH', 'ANIM', 'ANMF'};
const _webpMetadataFlags = 0x20 | 0x08 | 0x04;
/// Remove metadata from an animated image without decoding its frames.
///
/// Decoding through UIKit or Android Bitmap would flatten animations. These
/// structural scrubbers retain only the chunks/extensions accepted by the
/// relay and preserve animation timing, disposal, looping, and frame data.
Uint8List sanitizeAnimatedImageForUpload(Uint8List bytes, String mimeType) {
return switch (mimeType) {
'image/gif' => _scrubGif(bytes),
'image/png' => _scrubPng(bytes),
'image/webp' => _scrubWebp(bytes),
_ => throw FormatException('Unsupported animated image type: $mimeType'),
};
}
Uint8List _scrubPng(Uint8List bytes) {
if (!_startsWith(bytes, _pngSignature)) {
throw const FormatException('Invalid PNG signature');
}
final output = BytesBuilder(copy: false)..add(_pngSignature);
var offset = _pngSignature.length;
while (offset < bytes.length) {
if (bytes.length - offset < 12) {
throw const FormatException('Truncated PNG chunk');
}
final payloadLength = _readUint32BigEndian(bytes, offset);
final chunkLength = payloadLength + 12;
if (payloadLength > bytes.length - offset - 12) {
throw const FormatException('Invalid PNG chunk length');
}
final typeStart = offset + 4;
final type = ascii.decode(bytes.sublist(typeStart, typeStart + 4));
if (type == 'iCCP') {
throw const FormatException(
'Animated PNG ICC profile cannot be removed safely',
);
}
if (type == 'eXIf') {
final orientation = _readExifOrientation(
bytes,
offset + 8,
payloadLength,
);
if (orientation != null && orientation >= 2 && orientation <= 8) {
throw const FormatException(
'Animated PNG EXIF orientation cannot be removed safely',
);
}
}
final isAncillary = bytes[typeStart] & 0x20 != 0;
if (!isAncillary || _allowedPngAncillaryChunks.contains(type)) {
output.add(Uint8List.sublistView(bytes, offset, offset + chunkLength));
}
offset += chunkLength;
if (type == 'IEND') {
return output.takeBytes();
}
}
throw const FormatException('PNG is missing IEND');
}
Uint8List _scrubWebp(Uint8List bytes) {
if (bytes.length < 12 ||
!_matchesAscii(bytes, 0, 'RIFF') ||
!_matchesAscii(bytes, 8, 'WEBP')) {
throw const FormatException('Invalid WebP signature');
}
final declaredLength = _readUint32LittleEndian(bytes, 4);
final inputEnd = declaredLength + 8;
if (inputEnd < 12 || inputEnd > bytes.length) {
throw const FormatException('Invalid WebP container length');
}
final chunks = BytesBuilder(copy: false);
var offset = 12;
while (offset < inputEnd) {
if (inputEnd - offset < 8) {
throw const FormatException('Truncated WebP chunk');
}
final type = ascii.decode(bytes.sublist(offset, offset + 4));
final payloadLength = _readUint32LittleEndian(bytes, offset + 4);
final payloadStart = offset + 8;
final paddedLength = payloadLength + (payloadLength.isOdd ? 1 : 0);
final chunkEnd = payloadStart + paddedLength;
if (chunkEnd > inputEnd) {
throw const FormatException('Invalid WebP chunk length');
}
if (type == 'EXIF') {
final orientation = _readExifOrientation(
bytes,
payloadStart,
payloadLength,
);
if (orientation != null && orientation >= 2 && orientation <= 8) {
throw const FormatException(
'Animated WebP EXIF orientation cannot be removed safely',
);
}
}
if (type == 'ICCP') {
throw const FormatException(
'Animated WebP ICC profile cannot be removed safely',
);
}
if (_allowedWebpChunks.contains(type)) {
if (type == 'VP8X') {
if (payloadLength == 0) {
throw const FormatException('Invalid VP8X chunk');
}
final payload = BytesBuilder(copy: false)
..addByte(bytes[payloadStart] & ~_webpMetadataFlags)
..add(
Uint8List.sublistView(
bytes,
payloadStart + 1,
payloadStart + payloadLength,
),
);
_addWebpChunk(chunks, type, payload.takeBytes());
} else if (type == 'ANMF') {
_addWebpChunk(
chunks,
type,
_scrubAnmfPayload(
Uint8List.sublistView(
bytes,
payloadStart,
payloadStart + payloadLength,
),
),
);
} else {
_addWebpChunk(
chunks,
type,
Uint8List.sublistView(
bytes,
payloadStart,
payloadStart + payloadLength,
),
);
}
}
offset = chunkEnd;
}
final chunkBytes = chunks.takeBytes();
final output = BytesBuilder(copy: false)
..add(ascii.encode('RIFF'))
..add(_uint32LittleEndian(chunkBytes.length + 4))
..add(ascii.encode('WEBP'))
..add(chunkBytes);
return output.takeBytes();
}
void _addWebpChunk(BytesBuilder output, String type, Uint8List payload) {
output
..add(ascii.encode(type))
..add(_uint32LittleEndian(payload.length))
..add(payload);
if (payload.length.isOdd) output.addByte(0);
}
Uint8List _scrubAnmfPayload(Uint8List payload) {
const frameHeaderLength = 16;
if (payload.length < frameHeaderLength) {
throw const FormatException('Invalid WebP animation frame');
}
final output = BytesBuilder(copy: false)
..add(Uint8List.sublistView(payload, 0, frameHeaderLength));
var offset = frameHeaderLength;
var sawAlpha = false;
var sawImage = false;
while (offset < payload.length) {
if (payload.length - offset < 8) {
throw const FormatException('Truncated WebP animation frame chunk');
}
final chunkLength = _readUint32LittleEndian(payload, offset + 4);
final chunkStart = offset + 8;
final paddedLength = chunkLength + (chunkLength.isOdd ? 1 : 0);
final chunkEnd = chunkStart + paddedLength;
if (chunkEnd > payload.length) {
throw const FormatException('Invalid WebP animation frame chunk length');
}
final chunkPayload = Uint8List.sublistView(
payload,
chunkStart,
chunkStart + chunkLength,
);
if (_matchesAscii(payload, offset, 'ALPH')) {
if (sawAlpha || sawImage) {
throw const FormatException('Invalid WebP animation frame layout');
}
_addWebpChunk(output, 'ALPH', chunkPayload);
sawAlpha = true;
} else if (_matchesAscii(payload, offset, 'VP8 ')) {
if (sawImage) {
throw const FormatException('Invalid WebP animation frame layout');
}
_addWebpChunk(output, 'VP8 ', chunkPayload);
sawImage = true;
} else if (_matchesAscii(payload, offset, 'VP8L')) {
if (sawAlpha || sawImage) {
throw const FormatException('Invalid WebP animation frame layout');
}
_addWebpChunk(output, 'VP8L', chunkPayload);
sawImage = true;
}
offset = chunkEnd;
}
if (!sawImage) {
throw const FormatException('WebP animation frame is missing image data');
}
return output.takeBytes();
}
int? _readExifOrientation(
Uint8List bytes,
int payloadStart,
int payloadLength,
) {
final payloadEnd = payloadStart + payloadLength;
var tiffStart = payloadStart;
if (payloadLength >= 6 &&
_matchesAscii(bytes, payloadStart, 'Exif') &&
bytes[payloadStart + 4] == 0 &&
bytes[payloadStart + 5] == 0) {
tiffStart += 6;
}
if (payloadEnd - tiffStart < 8) return null;
final endian = switch ((bytes[tiffStart], bytes[tiffStart + 1])) {
(0x49, 0x49) => Endian.little,
(0x4d, 0x4d) => Endian.big,
_ => null,
};
if (endian == null) return null;
int? readUint16(int offset) {
if (offset < tiffStart || offset + 2 > payloadEnd) return null;
return ByteData.sublistView(bytes, offset, offset + 2).getUint16(0, endian);
}
int? readUint32(int offset) {
if (offset < tiffStart || offset + 4 > payloadEnd) return null;
return ByteData.sublistView(bytes, offset, offset + 4).getUint32(0, endian);
}
if (readUint16(tiffStart + 2) != 42) return null;
final ifdOffset = readUint32(tiffStart + 4);
if (ifdOffset == null) return null;
final ifdStart = tiffStart + ifdOffset;
final entryCount = readUint16(ifdStart);
if (entryCount == null) return null;
final entriesStart = ifdStart + 2;
for (var index = 0; index < entryCount; index += 1) {
final entryStart = entriesStart + index * 12;
if (readUint16(entryStart) == 0x0112 &&
readUint16(entryStart + 2) == 3 &&
readUint32(entryStart + 4) == 1) {
return readUint16(entryStart + 8);
}
}
return null;
}
Uint8List _scrubGif(Uint8List bytes) {
if (bytes.length < 13 ||
(!_matchesAscii(bytes, 0, 'GIF87a') &&
!_matchesAscii(bytes, 0, 'GIF89a'))) {
throw const FormatException('Invalid GIF signature');
}
var offset = 13;
final packed = bytes[10];
if (packed & 0x80 != 0) {
final tableLength = 3 << ((packed & 0x07) + 1);
offset += tableLength;
if (offset > bytes.length) {
throw const FormatException('Truncated GIF color table');
}
}
final segments = <Uint8List?>[Uint8List.sublistView(bytes, 0, offset)];
final pendingGraphicControls = <int>[];
while (offset < bytes.length) {
switch (bytes[offset]) {
case 0x2c:
final start = offset;
if (bytes.length - offset < 10) {
throw const FormatException('Truncated GIF image descriptor');
}
final imagePacked = bytes[offset + 9];
offset += 10;
if (imagePacked & 0x80 != 0) {
offset += 3 << ((imagePacked & 0x07) + 1);
if (offset > bytes.length) {
throw const FormatException('Truncated GIF local color table');
}
}
if (offset >= bytes.length) {
throw const FormatException('Missing GIF LZW code size');
}
offset = _gifSubBlocksEnd(bytes, offset + 1);
segments.add(Uint8List.sublistView(bytes, start, offset));
pendingGraphicControls.clear();
case 0x21:
final start = offset;
if (bytes.length - offset < 2) {
throw const FormatException('Truncated GIF extension');
}
final label = bytes[offset + 1];
offset += 2;
switch (label) {
case 0xf9:
if (bytes.length - offset < 6 ||
bytes[offset] != 4 ||
bytes[offset + 5] != 0) {
throw const FormatException('Invalid GIF graphic control');
}
offset += 6;
segments.add(Uint8List.sublistView(bytes, start, offset));
pendingGraphicControls.add(segments.length - 1);
case 0xff:
if (bytes.length - offset < 12 || bytes[offset] != 11) {
throw const FormatException('Invalid GIF application extension');
}
final isLoopExtension =
_matchesAscii(bytes, offset + 1, 'NETSCAPE2.0') ||
_matchesAscii(bytes, offset + 1, 'ANIMEXTS1.0');
final dataStart = offset + 12;
offset = _gifSubBlocksEnd(bytes, dataStart);
if (isLoopExtension) {
if (bytes.length - dataStart < 5 ||
bytes[dataStart] != 3 ||
bytes[dataStart + 1] != 1) {
throw const FormatException('Invalid GIF loop extension');
}
segments
..add(Uint8List.sublistView(bytes, start, dataStart + 4))
..add(Uint8List(1));
}
case 0x01:
offset = _gifSubBlocksEnd(bytes, offset);
for (final segmentIndex in pendingGraphicControls) {
segments[segmentIndex] = null;
}
pendingGraphicControls.clear();
default:
offset = _gifSubBlocksEnd(bytes, offset);
}
case 0x3b:
segments.add(Uint8List.sublistView(bytes, offset, offset + 1));
final output = BytesBuilder(copy: false);
for (final segment in segments) {
if (segment != null) output.add(segment);
}
return output.takeBytes();
default:
throw const FormatException('Invalid GIF block');
}
}
throw const FormatException('GIF is missing trailer');
}
int _gifSubBlocksEnd(Uint8List bytes, int offset) {
while (offset < bytes.length) {
final blockLength = bytes[offset];
offset += 1;
if (blockLength == 0) return offset;
offset += blockLength;
if (offset > bytes.length) {
throw const FormatException('Truncated GIF data block');
}
}
throw const FormatException('GIF data blocks are missing a terminator');
}
bool _startsWith(Uint8List bytes, List<int> prefix) {
if (bytes.length < prefix.length) return false;
for (var index = 0; index < prefix.length; index += 1) {
if (bytes[index] != prefix[index]) return false;
}
return true;
}
bool _matchesAscii(Uint8List bytes, int offset, String value) {
final expected = ascii.encode(value);
if (bytes.length - offset < expected.length) return false;
for (var index = 0; index < expected.length; index += 1) {
if (bytes[offset + index] != expected[index]) return false;
}
return true;
}
int _readUint32BigEndian(Uint8List bytes, int offset) {
return ByteData.sublistView(bytes, offset, offset + 4).getUint32(0);
}
int _readUint32LittleEndian(Uint8List bytes, int offset) {
return ByteData.sublistView(
bytes,
offset,
offset + 4,
).getUint32(0, Endian.little);
}
Uint8List _uint32LittleEndian(int value) {
return Uint8List(4)..buffer.asByteData().setUint32(0, value, Endian.little);
}