Upload text blobs with explicit charset=utf-8 so browsers decode UTF-8 correctly (em dashes rendered as mojibake on charset-less text/plain); keep NIP-94 m tag as the bare semantic MIME type
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "nostr-publish",
|
||||
"displayName": "Nostr Publish",
|
||||
"description": "Publish files to Nostr from Codium/VSCode. Phase 0: raw unsigned event JSON. Phase 1: long-form notes (.md). Phase 2: signer (Rust n_signer port) signing backend.",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"publisher": "laantungir",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
+30
-7
@@ -1860,6 +1860,20 @@ function resolveBlossomServers(): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an explicit `charset=utf-8` parameter to `text/*` MIME types.
|
||||
*
|
||||
* Blossom servers store the upload `Content-Type` and echo it verbatim when
|
||||
* serving the blob. Without a charset parameter, browsers fall back to
|
||||
* Windows-1252 for text responses, rendering UTF-8 content as mojibake (an
|
||||
* em dash `—` shows as `â€"`). Non-text types are returned unchanged.
|
||||
*/
|
||||
function withUtf8Charset(mime: string): string {
|
||||
if (!mime.startsWith("text/")) return mime;
|
||||
if (/;\s*charset=/i.test(mime)) return mime;
|
||||
return `${mime}; charset=utf-8`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared upload + publish core for both the quick upload and header-based
|
||||
* publish commands.
|
||||
@@ -1870,11 +1884,15 @@ function resolveBlossomServers(): string[] {
|
||||
* 4. Builds a kind 1063 NIP-94 event from the first successful descriptor.
|
||||
* 5. Signs and publishes the 1063 event to the enabled relays.
|
||||
*
|
||||
* Markdown bodies are uploaded as `text/plain` (browsers render text/plain
|
||||
* inline; no mainstream browser displays text/markdown, and servers send
|
||||
* `X-Content-Type-Options: nosniff` which forbids guessing). The kind 1063
|
||||
* event's `m` tag still advertises `text/markdown` so Nostr clients that
|
||||
* render markdown can detect the file's true semantic type.
|
||||
* Markdown bodies are uploaded as `text/plain; charset=utf-8` (browsers
|
||||
* render text/plain inline; no mainstream browser displays text/markdown,
|
||||
* and servers send `X-Content-Type-Options: nosniff` which forbids
|
||||
* guessing). The explicit charset is required: Blossom servers echo the
|
||||
* upload `Content-Type` verbatim when serving the blob, and without it
|
||||
* browsers fall back to Windows-1252, rendering UTF-8 text as mojibake
|
||||
* (e.g. an em dash `—` becomes `â€"`). The kind 1063 event's `m` tag still
|
||||
* advertises the bare semantic type (`text/markdown`) so Nostr clients
|
||||
* that render markdown can detect the file's true type.
|
||||
*/
|
||||
async function uploadAndPublishMetadata(
|
||||
state: StateService,
|
||||
@@ -1885,9 +1903,14 @@ async function uploadAndPublishMetadata(
|
||||
): Promise<void> {
|
||||
// Hybrid MIME handling: markdown uploads go out as text/plain for
|
||||
// browser-renderability; the semantic type is preserved for the m tag.
|
||||
// Text bodies carry an explicit charset=utf-8 (see withUtf8Charset).
|
||||
const isMarkdown = mime === "text/markdown";
|
||||
const uploadMime = isMarkdown ? "text/plain" : mime;
|
||||
const semanticMime = isMarkdown ? "text/markdown" : undefined;
|
||||
const uploadMime = withUtf8Charset(isMarkdown ? "text/plain" : mime);
|
||||
// The m tag must carry the bare semantic MIME (no charset parameter):
|
||||
// markdown → text/markdown; other text types → the bare type, since the
|
||||
// server-reported descriptor type echoes the charset-annotated upload
|
||||
// Content-Type. Binary types pass through unchanged (no override needed).
|
||||
const semanticMime = isMarkdown || uploadMime !== mime ? mime : undefined;
|
||||
|
||||
// Require sign-in.
|
||||
let signer = getSigner(state, config);
|
||||
|
||||
+10
-6
@@ -16,12 +16,16 @@ export interface FileMetadataInput {
|
||||
/**
|
||||
* Override for the `m` tag (the file's semantic MIME type).
|
||||
*
|
||||
* Markdown blobs are uploaded as `text/plain` so browsers render them
|
||||
* inline (no mainstream browser displays `text/markdown`, and
|
||||
* `X-Content-Type-Options: nosniff` forbids guessing). The server then
|
||||
* reports `type: text/plain` in the descriptor. This override keeps the
|
||||
* Nostr `m` tag advertising the true semantic type (`text/markdown`) so
|
||||
* clients that render markdown can still detect it.
|
||||
* Markdown blobs are uploaded as `text/plain; charset=utf-8` so browsers
|
||||
* render them inline (no mainstream browser displays `text/markdown`, and
|
||||
* `X-Content-Type-Options: nosniff` forbids guessing). Text uploads carry
|
||||
* an explicit charset because Blossom servers echo the upload
|
||||
* `Content-Type` verbatim when serving blobs — without it browsers fall
|
||||
* back to Windows-1252 and render UTF-8 text as mojibake. The server then
|
||||
* reports the charset-annotated type (e.g. `text/plain; charset=utf-8`) in
|
||||
* the descriptor. This override keeps the Nostr `m` tag advertising the
|
||||
* bare semantic type (`text/markdown`, `text/plain`, …) so clients that
|
||||
* render markdown can still detect it.
|
||||
*/
|
||||
mimeType?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user