safeer

[email protected]
2026-08-07 18:34:16 +0500 committed NaN years ago
6086f7b
commit 6086f7b
2 files changed
+63 -10
▼opengrammar/backend/src/index.ts
+13-3
1
1
import { Hono } from 'hono';
2
2
import { cors } from 'hono/cors';
3
3
import { logger } from 'hono/logger';
4
+import type { Context, Next } from 'hono';
4
5
import OpenAI from 'openai';
5
6
import { LLMAnalyzer, RuleBasedAnalyzer } from './analyzer.js';
6
7
import { analyzeTone } from './nlp/tone-analyzer.js';
21
22
// Middleware
22
23
app.use('/*', logger());
23
24
app.use('/*', cors());
24
-app.use('/*', async (c, next) => {
25
+
26
+// Append charset=utf-8 to JSON responses that don't declare one.
27
+// Content-Type is case-insensitive: media type and charset parameter are
28
+// matched case-insensitively so an existing "Charset=iso-8859-1" is preserved
29
+// instead of appending a conflicting charset.
30
+export async function ensureJsonUtf8(c: Context, next: Next) {
25
31
await next();
26
32
const ct = c.res.headers.get('Content-Type') ?? '';
27
- if (ct.startsWith('application/json') && !ct.includes('charset')) {
33
+ const [mediaType, ...parameters] = ct.split(';');
34
+ const hasCharset = parameters.some((p) => /^\s*charset\s*=/i.test(p));
35
+ if (mediaType.trim().toLowerCase() === 'application/json' && !hasCharset) {
28
36
c.res.headers.set('Content-Type', ct + '; charset=utf-8');
29
37
}
30
-});
38
+}
39
+
40
+app.use('/*', ensureJsonUtf8);
31
41
32
42
// Root Landing Page
33
43
app.get('/', (c) => {
34
44
▼opengrammar/backend/tests/server.test.ts
+50-7
1
1
import { describe, expect, test } from 'bun:test';
2
-import app from '../src/index.js';
2
+import { Hono } from 'hono';
3
+import app, { ensureJsonUtf8 } from '../src/index.js';
4
+
5
+function fixtureApp() {
6
+ const fixture = new Hono();
7
+ fixture.use('/*', ensureJsonUtf8);
8
+ fixture.get('/json', (c) =>
9
+ new Response('{"a":1}', { headers: { 'Content-Type': 'application/json' } }),
10
+ );
11
+ fixture.get('/json-lower-charset', (c) =>
12
+ new Response('{"a":1}', {
13
+ headers: { 'Content-Type': 'application/json; charset=iso-8859-1' },
14
+ }),
15
+ );
16
+ fixture.get('/json-mixed-charset', (c) =>
17
+ new Response('{"a":1}', { headers: { 'Content-Type': 'application/json; Charset=UTF-8' } }),
18
+ );
19
+ fixture.get('/json-patch', (c) =>
20
+ new Response('{"a":1}', { headers: { 'Content-Type': 'application/json-patch+json' } }),
21
+ );
22
+ fixture.get('/html', (c) =>
23
+ new Response('<h1>hi</h1>', { headers: { 'Content-Type': 'text/html; charset=utf-8' } }),
24
+ );
25
+ return fixture;
26
+}
3
27
4
28
describe('response Content-Type charset', () => {
5
- test('appends charset=utf-8 to JSON responses', async () => {
29
+ test('appends charset=utf-8 to real JSON responses', async () => {
6
30
const res = await app.request('/health');
7
31
expect(res.status).toBe(200);
8
- expect(res.headers.get('Content-Type')).toMatch(/application\/json;\s*charset=utf-8/i);
32
+ expect(res.headers.get('Content-Type')).toBe('application/json; charset=utf-8');
33
+ });
34
+
35
+ test('appends charset=utf-8 to charset-less JSON', async () => {
36
+ const res = await fixtureApp().request('/json');
37
+ expect(res.headers.get('Content-Type')).toBe('application/json; charset=utf-8');
38
+ });
39
+
40
+ test('preserves an existing lower-case charset', async () => {
41
+ const res = await fixtureApp().request('/json-lower-charset');
42
+ expect(res.headers.get('Content-Type')).toBe('application/json; charset=iso-8859-1');
43
+ });
44
+
45
+ test('preserves a mixed-case charset without appending a conflicting one', async () => {
46
+ const res = await fixtureApp().request('/json-mixed-charset');
47
+ expect(res.headers.get('Content-Type')).toBe('application/json; Charset=UTF-8');
48
+ });
49
+
50
+ test('does not touch other application/* media types', async () => {
51
+ const res = await fixtureApp().request('/json-patch');
52
+ expect(res.headers.get('Content-Type')).toBe('application/json-patch+json');
9
53
});
10
54
11
55
test('does not modify non-JSON responses', async () => {
12
- const res = await app.request('/');
56
+ const res = await fixtureApp().request('/html');
13
57
expect(res.status).toBe(200);
14
- expect(res.headers.get('Content-Type')).toStartWith('text/html');
15
- const body = await res.text();
16
- expect(body.length).toBeGreaterThan(0);
58
+ expect(res.headers.get('Content-Type')).toBe('text/html; charset=utf-8');
59
+ expect(await res.text()).toBe('<h1>hi</h1>');
17
60
});
18
61
});
19
62