Skip to content

Commit 8c31a05

Browse files
committed
Ensure grpc+json is handled as JSON, not protobuf
1 parent 186acbd commit 8c31a05

File tree

4 files changed

+45
-15
lines changed

4 files changed

+45
-15
lines changed

src/model/events/body-formatting.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,11 @@ export const Formatters: { [key in ViewableContentType]: Formatter } = {
142142
isEditApplicable: false,
143143
render: buildAsyncRenderer('protobuf')
144144
},
145-
grpc: {
146-
language: 'grpc',
147-
cacheKey: Symbol('grpc'),
145+
'grpc-proto': {
146+
language: 'protobuf',
147+
cacheKey: Symbol('grpc-proto'),
148148
isEditApplicable: false,
149-
render: buildAsyncRenderer('grpc')
149+
render: buildAsyncRenderer('grpc-proto')
150150
},
151151
'url-encoded': {
152152
layout: 'scrollable',

src/model/events/content-types.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,32 @@ import {
88
// Simplify a mime type as much as we can, without throwing any errors
99
export const getBaseContentType = (mimeType: string | undefined) => {
1010
const typeWithoutParams = (mimeType || '').split(';')[0];
11-
const [type, combinedSubTypes] = typeWithoutParams.split(/\/(.+)/);
1211

12+
let [type, combinedSubTypes] = typeWithoutParams.split(/\/(.+)/);
1313
if (!combinedSubTypes) return type;
1414

15-
// A list of types from most specific to most generic: [svg, xml] for image/svg+xml
16-
const subTypes = combinedSubTypes.split('+');
15+
if (DEFAULT_SUBTYPE[combinedSubTypes]) {
16+
combinedSubTypes = `${combinedSubTypes}+${DEFAULT_SUBTYPE[combinedSubTypes]}`;
17+
}
18+
19+
// If this is a known type with an exact match, return that directly:
20+
if (mimeTypeToContentTypeMap[type + '/' + combinedSubTypes]) {
21+
return type + '/' + combinedSubTypes;
22+
}
1723

24+
// Otherwise, wr collect a list of types from most specific to most generic: [svg, xml] for image/svg+xml
25+
// and then look through in order to see if there are any matches here:
26+
const subTypes = combinedSubTypes.split('+');
1827
const possibleTypes = subTypes.map(st => type + '/' + st);
19-
return _.find(possibleTypes, t => !!mimeTypeToContentTypeMap[t]) ||
28+
29+
return _.find(possibleTypes, t => !!mimeTypeToContentTypeMap[t]) || // Subtype match
2030
_.last(possibleTypes)!; // If we recognize none - return the most generic
2131
}
2232

33+
const DEFAULT_SUBTYPE: { [type: string]: string } = {
34+
'grpc': 'proto' // Protobuf is the default gRPC content type (but not the only one!)
35+
};
36+
2337
export type ViewableContentType =
2438
| 'raw'
2539
| 'text'
@@ -34,7 +48,7 @@ export type ViewableContentType =
3448
| 'yaml'
3549
| 'image'
3650
| 'protobuf'
37-
| 'grpc';
51+
| 'grpc-proto';
3852

3953
export const EditableContentTypes = [
4054
'text',
@@ -97,7 +111,7 @@ const mimeTypeToContentTypeMap: { [mimeType: string]: ViewableContentType } = {
97111
'application/proto': 'protobuf', // N.b. this covers all application/XXX+proto values
98112
'application/x-protobuffer': 'protobuf', // Commonly seen in Google apps
99113

100-
'application/grpc': 'grpc', // Used in GRPC requests (protobuf with special headers)
114+
'application/grpc+proto': 'grpc-proto', // Used in GRPC requests (protobuf but with special headers)
101115

102116
'application/octet-stream': 'raw'
103117
} as const;
@@ -124,7 +138,7 @@ export function getContentEditorName(contentType: ViewableContentType): string {
124138
: contentType === 'json' ? 'JSON'
125139
: contentType === 'css' ? 'CSS'
126140
: contentType === 'url-encoded' ? 'URL-Encoded'
127-
: contentType === 'grpc' ? 'gRPC'
141+
: contentType === 'grpc-proto' ? 'gRPC'
128142
: _.capitalize(contentType);
129143
}
130144

@@ -166,14 +180,15 @@ export function getCompatibleTypes(
166180
types.add('xml');
167181
}
168182

169-
if (!types.has('grpc') && rawContentType && rawContentType === 'application/grpc') {
170-
types.add('grpc')
183+
if (!types.has('grpc-proto') && rawContentType === 'application/grpc') {
184+
types.add('grpc-proto')
171185
}
186+
172187
if (
173188
body &&
174189
isProbablyProtobuf(body) &&
175190
!types.has('protobuf') &&
176-
!types.has('grpc') &&
191+
!types.has('grpc-proto') &&
177192
// If it's probably unmarked protobuf, and it's a manageable size, try
178193
// parsing it just to check:
179194
(body.length < 100_000 && isValidProtobuf(body))

src/services/ui-worker-formatters.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ const WorkerFormatters = {
9393
}
9494
}, 2);
9595
},
96-
grpc: (content: Buffer) => {
96+
'grpc-proto': (content: Buffer) => {
9797
const data = parseGrpcProtobuf(content);
9898

9999
return JSON.stringify(data, (_key, value) => {

test/unit/model/http/content-types.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ describe('Content type parsing', () => {
4949
expect(ct).to.equal('raw');
5050
});
5151

52+
it('should render application/grpc as protobuf grpc', () => {
53+
const ct = getContentType('application/grpc');
54+
expect(ct).to.equal('grpc-proto');
55+
});
56+
57+
it('should render application/grpc+proto as protobuf grpc', () => {
58+
const ct = getContentType('application/grpc+proto');
59+
expect(ct).to.equal('grpc-proto');
60+
});
61+
62+
it('should render application/grpc+json as JSON', () => {
63+
const ct = getContentType('application/grpc+json');
64+
expect(ct).to.equal('json');
65+
});
66+
5267
it('should return undefined for unknown content', () => {
5368
const ct = getContentType('application/unknownsomething');
5469
expect(ct).to.equal(undefined);

0 commit comments

Comments
 (0)