Commit a0c6afa0 by Hugo Häggmark Committed by Arve Knudsen

Fix: fixes issue with headers property with different casing (#22778)

Fixes #22756

(cherry picked from commit b30f4c7b)
parent 3d0bc141
...@@ -589,12 +589,10 @@ export const parseUrlFromOptions = (options: BackendSrvRequest): string => { ...@@ -589,12 +589,10 @@ export const parseUrlFromOptions = (options: BackendSrvRequest): string => {
export const parseInitFromOptions = (options: BackendSrvRequest): RequestInit => { export const parseInitFromOptions = (options: BackendSrvRequest): RequestInit => {
const method = options.method; const method = options.method;
const headers = { const headers = parseHeaders(options);
'Content-Type': 'application/json', const isAppJson = isContentTypeApplicationJson(headers);
Accept: 'application/json, text/plain, */*', const body = parseBody(options, isAppJson);
...options.headers,
};
const body = parseBody({ ...options, headers });
return { return {
method, method,
headers, headers,
...@@ -602,14 +600,42 @@ export const parseInitFromOptions = (options: BackendSrvRequest): RequestInit => ...@@ -602,14 +600,42 @@ export const parseInitFromOptions = (options: BackendSrvRequest): RequestInit =>
}; };
}; };
const parseBody = (options: BackendSrvRequest) => { export const parseHeaders = (options: BackendSrvRequest) => {
if (!options.data || typeof options.data === 'string') { const headers = new Headers({
return options.data; 'Content-Type': 'application/json',
Accept: 'application/json, text/plain, */*',
});
if (options && options.headers) {
Object.keys(options.headers).forEach(key => {
headers.set(key, options.headers[key]);
});
} }
if (options.headers['Content-Type'] === 'application/json') { return headers;
return JSON.stringify(options.data); };
export const isContentTypeApplicationJson = (headers: Headers) => {
if (!headers) {
return false;
}
const contentType = headers.get('content-type');
if (contentType && contentType.toLowerCase() === 'application/json') {
return true;
}
return false;
};
export const parseBody = (options: BackendSrvRequest, isAppJson: boolean) => {
if (!options) {
return options;
}
if (!options.data || typeof options.data === 'string') {
return options.data;
} }
return new URLSearchParams(options.data); return isAppJson ? JSON.stringify(options.data) : new URLSearchParams(options.data);
}; };
import { BackendSrv, getBackendSrv, parseInitFromOptions, parseUrlFromOptions } from '../services/backend_srv'; import 'whatwg-fetch'; // fetch polyfill needed for PhantomJs rendering
import { Emitter } from '../utils/emitter';
import { ContextSrv, User } from '../services/context_srv';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { AppEvents } from '@grafana/data'; import { AppEvents } from '@grafana/data';
import {
BackendSrv,
getBackendSrv,
isContentTypeApplicationJson,
parseBody,
parseHeaders,
parseInitFromOptions,
parseUrlFromOptions,
} from '../services/backend_srv';
import { Emitter } from '../utils/emitter';
import { ContextSrv, User } from '../services/context_srv';
import { CoreEvents } from '../../types'; import { CoreEvents } from '../../types';
import { delay } from 'rxjs/operators';
const getTestContext = (overides?: object) => { const getTestContext = (overides?: object) => {
const defaults = { const defaults = {
...@@ -174,7 +184,9 @@ describe('backendSrv', () => { ...@@ -174,7 +184,9 @@ describe('backendSrv', () => {
statusText: 'Ok', statusText: 'Ok',
text: () => Promise.resolve(JSON.stringify(slowData)), text: () => Promise.resolve(JSON.stringify(slowData)),
headers: { headers: {
'Content-Type': 'application/json', map: {
'content-type': 'application/json',
},
}, },
redirected: false, redirected: false,
type: 'basic', type: 'basic',
...@@ -189,7 +201,9 @@ describe('backendSrv', () => { ...@@ -189,7 +201,9 @@ describe('backendSrv', () => {
statusText: 'Ok', statusText: 'Ok',
text: () => Promise.resolve(JSON.stringify(fastData)), text: () => Promise.resolve(JSON.stringify(fastData)),
headers: { headers: {
'Content-Type': 'application/json', map: {
'content-type': 'application/json',
},
}, },
redirected: false, redirected: false,
type: 'basic', type: 'basic',
...@@ -358,8 +372,10 @@ describe('backendSrv', () => { ...@@ -358,8 +372,10 @@ describe('backendSrv', () => {
method: 'GET', method: 'GET',
body: undefined, body: undefined,
headers: { headers: {
'Content-Type': 'application/json', map: {
Accept: 'application/json, text/plain, */*', 'content-type': 'application/json',
accept: 'application/json, text/plain, */*',
},
}, },
}, },
}); });
...@@ -389,8 +405,10 @@ describe('backendSrv', () => { ...@@ -389,8 +405,10 @@ describe('backendSrv', () => {
method: 'GET', method: 'GET',
body: undefined as any, body: undefined as any,
headers: { headers: {
'Content-Type': 'application/json', map: {
Accept: 'application/json, text/plain, */*', 'content-type': 'application/json',
accept: 'application/json, text/plain, */*',
},
}, },
}, },
}; };
...@@ -415,7 +433,9 @@ describe('backendSrv', () => { ...@@ -415,7 +433,9 @@ describe('backendSrv', () => {
statusText: 'Ok', statusText: 'Ok',
text: () => Promise.resolve(JSON.stringify(slowData)), text: () => Promise.resolve(JSON.stringify(slowData)),
headers: { headers: {
'Content-Type': 'application/json', map: {
'content-type': 'application/json',
},
}, },
redirected: false, redirected: false,
type: 'basic', type: 'basic',
...@@ -430,7 +450,9 @@ describe('backendSrv', () => { ...@@ -430,7 +450,9 @@ describe('backendSrv', () => {
statusText: 'Ok', statusText: 'Ok',
text: () => Promise.resolve(JSON.stringify(fastData)), text: () => Promise.resolve(JSON.stringify(fastData)),
headers: { headers: {
'Content-Type': 'application/json', map: {
'content-type': 'application/json',
},
}, },
redirected: false, redirected: false,
type: 'basic', type: 'basic',
...@@ -448,7 +470,9 @@ describe('backendSrv', () => { ...@@ -448,7 +470,9 @@ describe('backendSrv', () => {
expect(fastResponse).toEqual({ expect(fastResponse).toEqual({
data: { message: 'Fast Request' }, data: { message: 'Fast Request' },
headers: { headers: {
'Content-Type': 'application/json', map: {
'content-type': 'application/json',
},
}, },
ok: true, ok: true,
redirected: false, redirected: false,
...@@ -461,8 +485,10 @@ describe('backendSrv', () => { ...@@ -461,8 +485,10 @@ describe('backendSrv', () => {
method: 'GET', method: 'GET',
body: undefined, body: undefined,
headers: { headers: {
'Content-Type': 'application/json', map: {
Accept: 'application/json, text/plain, */*', 'content-type': 'application/json',
accept: 'application/json, text/plain, */*',
},
}, },
}, },
}); });
...@@ -477,8 +503,10 @@ describe('backendSrv', () => { ...@@ -477,8 +503,10 @@ describe('backendSrv', () => {
method: 'GET', method: 'GET',
body: undefined, body: undefined,
headers: { headers: {
'Content-Type': 'application/json', map: {
Accept: 'application/json, text/plain, */*', 'content-type': 'application/json',
accept: 'application/json, text/plain, */*',
},
}, },
}, },
}); });
...@@ -634,22 +662,65 @@ describe('parseUrlFromOptions', () => { ...@@ -634,22 +662,65 @@ describe('parseUrlFromOptions', () => {
describe('parseInitFromOptions', () => { describe('parseInitFromOptions', () => {
it.each` it.each`
method | headers | data | expected method | expected
${undefined} | ${undefined} | ${undefined} | ${{ method: undefined, headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*' }, body: undefined }} ${undefined} | ${{ method: undefined, headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
${'GET'} | ${undefined} | ${undefined} | ${{ method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*' }, body: undefined }} ${'GET'} | ${{ method: 'GET', headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
${'GET'} | ${undefined} | ${null} | ${{ method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*' }, body: null }} ${'POST'} | ${{ method: 'POST', headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
${'GET'} | ${{ Auth: 'Some Auth' }} | ${undefined} | ${{ method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*', Auth: 'Some Auth' }, body: undefined }} ${'monkey'} | ${{ method: 'monkey', headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
${'GET'} | ${{ Auth: 'Some Auth' }} | ${{ data: { test: 'Some data' } }} | ${{ method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*', Auth: 'Some Auth' }, body: '{"data":{"test":"Some data"}}' }}
${'GET'} | ${{ Auth: 'Some Auth' }} | ${'some data'} | ${{ method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*', Auth: 'Some Auth' }, body: 'some data' }}
${'GET'} | ${{ Auth: 'Some Auth' }} | ${'{"data":{"test":"Some data"}}'} | ${{ method: 'GET', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*', Auth: 'Some Auth' }, body: '{"data":{"test":"Some data"}}' }}
${'POST'} | ${{ Auth: 'Some Auth', 'Content-Type': 'application/x-www-form-urlencoded' }} | ${undefined} | ${{ method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json, text/plain, */*', Auth: 'Some Auth' }, body: undefined }}
${'POST'} | ${{ Auth: 'Some Auth', 'Content-Type': 'application/x-www-form-urlencoded' }} | ${{ data: 'Some data' }} | ${{ method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json, text/plain, */*', Auth: 'Some Auth' }, body: new URLSearchParams({ data: 'Some data' }) }}
${'POST'} | ${{ Auth: 'Some Auth', 'Content-Type': 'application/x-www-form-urlencoded' }} | ${'some data'} | ${{ method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json, text/plain, */*', Auth: 'Some Auth' }, body: 'some data' }}
${'POST'} | ${{ Auth: 'Some Auth', 'Content-Type': 'application/x-www-form-urlencoded' }} | ${'{"data":{"test":"Some data"}}'} | ${{ method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json, text/plain, */*', Auth: 'Some Auth' }, body: '{"data":{"test":"Some data"}}' }}
`( `(
"when called with method: '$method', headers: '$headers' and data: '$data' then result should be '$expected'", "when called with method: '$method', headers: '$headers' and data: '$data' then result should be '$expected'",
({ method, headers, data, expected }) => { ({ method, expected }) => {
expect(parseInitFromOptions({ method, headers, data, url: '' })).toEqual(expected); expect(parseInitFromOptions({ method, data: { id: '0' }, url: '' })).toEqual(expected);
}
);
});
describe('parseHeaders', () => {
it.each`
options | expected
${undefined} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
${{ propKey: 'some prop value' }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
${{ headers: { 'content-type': 'application/json' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
${{ headers: { 'cOnTent-tYpe': 'application/json' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
${{ headers: { 'content-type': 'AppLiCatIon/JsOn' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'AppLiCatIon/JsOn' } }}
${{ headers: { 'cOnTent-tYpe': 'AppLiCatIon/JsOn' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'AppLiCatIon/JsOn' } }}
${{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/x-www-form-urlencoded' } }}
${{ headers: { Accept: 'text/plain' } }} | ${{ map: { accept: 'text/plain', 'content-type': 'application/json' } }}
${{ headers: { Auth: 'Basic asdasdasd' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json', auth: 'Basic asdasdasd' } }}
`("when called with options: '$options' then the result should be '$expected'", ({ options, expected }) => {
expect(parseHeaders(options)).toEqual(expected);
});
});
describe('isContentTypeApplicationJson', () => {
it.each`
headers | expected
${undefined} | ${false}
${new Headers({ 'cOnTent-tYpe': 'application/json' })} | ${true}
${new Headers({ 'content-type': 'AppLiCatIon/JsOn' })} | ${true}
${new Headers({ 'cOnTent-tYpe': 'AppLiCatIon/JsOn' })} | ${true}
${new Headers({ 'content-type': 'application/x-www-form-urlencoded' })} | ${false}
${new Headers({ auth: 'Basic akdjasdkjalksdjasd' })} | ${false}
`("when called with headers: 'headers' then the result should be '$expected'", ({ headers, expected }) => {
expect(isContentTypeApplicationJson(headers)).toEqual(expected);
});
});
describe('parseBody', () => {
it.each`
options | isAppJson | expected
${undefined} | ${false} | ${undefined}
${undefined} | ${true} | ${undefined}
${{ data: undefined }} | ${false} | ${undefined}
${{ data: undefined }} | ${true} | ${undefined}
${{ data: 'some data' }} | ${false} | ${'some data'}
${{ data: 'some data' }} | ${true} | ${'some data'}
${{ data: { id: '0' } }} | ${false} | ${new URLSearchParams({ id: '0' })}
${{ data: { id: '0' } }} | ${true} | ${'{"id":"0"}'}
`(
"when called with options: '$options' and isAppJson: '$isAppJson' then the result should be '$expected'",
({ options, isAppJson, expected }) => {
expect(parseBody(options, isAppJson)).toEqual(expected);
} }
); );
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment