From 01baf441b24dd58d47545aac22c3f871089db1bf Mon Sep 17 00:00:00 2001 From: Axel Bocciarelli Date: Fri, 23 Aug 2024 10:02:07 +0200 Subject: [PATCH] Check MIME type before parsing responses as JSON --- ui/src/api/addons/safeJson.js | 28 ++++++++++++++++++++++++++++ ui/src/api/beamline.js | 6 +++--- ui/src/api/detector.js | 6 ++++-- ui/src/api/diffractometer.js | 2 +- ui/src/api/harvester.js | 18 ++++++++++-------- ui/src/api/index.js | 2 ++ ui/src/api/lims.js | 4 ++-- ui/src/api/log.js | 2 +- ui/src/api/login.js | 4 ++-- ui/src/api/main.js | 4 ++-- ui/src/api/queue.js | 16 ++++++++-------- ui/src/api/remoteAccess.js | 4 ++-- ui/src/api/sampleChanger.js | 14 +++++++------- ui/src/api/sampleview.js | 12 ++++++------ ui/src/api/workflow.js | 2 +- 15 files changed, 79 insertions(+), 45 deletions(-) create mode 100644 ui/src/api/addons/safeJson.js diff --git a/ui/src/api/addons/safeJson.js b/ui/src/api/addons/safeJson.js new file mode 100644 index 000000000..372ef3277 --- /dev/null +++ b/ui/src/api/addons/safeJson.js @@ -0,0 +1,28 @@ +/* eslint-disable promise/prefer-await-to-then */ + +function safeJsonMiddleware(state) { + return (next) => (url, opts) => { + return next(url, opts).then((response) => { + const contentType = response.headers.get('content-type'); + if (state.safeJson && contentType !== 'application/json') { + throw new Error(`Expected JSON response but got ${contentType}`); + } + + return response; + }); + }; +} + +const safeJson = () => ({ + beforeRequest(wretch, _, state) { + return wretch.middlewares([safeJsonMiddleware(state)]); + }, + resolver: { + safeJson() { + this._sharedState.safeJson = true; + return this.json(); + }, + }, +}); + +export default safeJson; diff --git a/ui/src/api/beamline.js b/ui/src/api/beamline.js index 7315da94a..0475af15d 100644 --- a/ui/src/api/beamline.js +++ b/ui/src/api/beamline.js @@ -3,11 +3,11 @@ import api from '.'; const endpoint = api.url('/beamline'); export function fetchBeamlineSetup() { - return endpoint.get('/').json(); + return endpoint.get('/').safeJson(); } export function fetchBeamInfo() { - return endpoint.get('/beam/info').json(); + return endpoint.get('/beam/info').safeJson(); } export function sendPrepareBeamlineForNewSample() { @@ -23,7 +23,7 @@ export function sendSetAttribute(name, type, value) { } export function sendGetAttribute(type, name, attr, args) { - return endpoint.post(args, `/${type}/${name}/${attr}`).json(); + return endpoint.post(args, `/${type}/${name}/${attr}`).safeJson(); } export function sendRunBeamlineAction(name, parameters) { diff --git a/ui/src/api/detector.js b/ui/src/api/detector.js index 45acba38f..7b3ea8d2b 100644 --- a/ui/src/api/detector.js +++ b/ui/src/api/detector.js @@ -3,9 +3,11 @@ import api from '.'; const endpoint = api.url('/detector'); export function fetchDetectorInfo() { - return endpoint.get('/').json(); + return endpoint.get('/').safeJson(); } export function fetchDisplayImage(path, imgNum) { - return endpoint.get(`/display_image?path=${path}&img_num=${imgNum}`).json(); + return endpoint + .get(`/display_image?path=${path}&img_num=${imgNum}`) + .safeJson(); } diff --git a/ui/src/api/diffractometer.js b/ui/src/api/diffractometer.js index bf534da5d..edd0fd5ae 100644 --- a/ui/src/api/diffractometer.js +++ b/ui/src/api/diffractometer.js @@ -3,7 +3,7 @@ import api from '.'; const endpoint = api.url('/diffractometer'); export function fetchDiffractometerInfo() { - return endpoint.get('/info').json(); + return endpoint.get('/info').safeJson(); } export function sendUpdateCurrentPhase(phase) { diff --git a/ui/src/api/harvester.js b/ui/src/api/harvester.js index 3d6b09caa..4aea7ccef 100644 --- a/ui/src/api/harvester.js +++ b/ui/src/api/harvester.js @@ -3,33 +3,35 @@ import api from '.'; const endpoint = api.url('/harvester'); export function fetchHarvesterInitialState() { - return endpoint.get('/get_harvester_initial_state').json(); + return endpoint.get('/get_harvester_initial_state').safeJson(); } export function sendRefresh() { - return endpoint.get('/contents').json(); + return endpoint.get('/contents').safeJson(); } export function sendHarvestCrystal(xtalUUID) { - return endpoint.post(JSON.stringify(xtalUUID), '/harvest').json(); + return endpoint.post(JSON.stringify(xtalUUID), '/harvest').safeJson(); } export function sendHarvestAndLoadCrystal(xtalUUID) { - return endpoint.post(JSON.stringify(xtalUUID), '/harvest_and_mount').json(); + return endpoint + .post(JSON.stringify(xtalUUID), '/harvest_and_mount') + .safeJson(); } export function sendCalibratePin() { - return endpoint.get('/calibrate').json(); + return endpoint.get('/calibrate').safeJson(); } export function sendDataCollectionInfoToCrims() { - return endpoint.get('/send_data_collection_info_to_crims').json(); + return endpoint.get('/send_data_collection_info_to_crims').safeJson(); } export function sendValidateCalibration(validated) { return endpoint .post(JSON.stringify(validated), '/validate_calibration') - .json(); + .safeJson(); } export function sendAbortHarvester() { @@ -37,5 +39,5 @@ export function sendAbortHarvester() { } export function sendHarvesterCommand(cmdparts, args) { - return endpoint.get(`/send_command/${cmdparts}/${args}`).json(); + return endpoint.get(`/send_command/${cmdparts}/${args}`).safeJson(); } diff --git a/ui/src/api/index.js b/ui/src/api/index.js index 2de5959d1..83fb03938 100644 --- a/ui/src/api/index.js +++ b/ui/src/api/index.js @@ -1,6 +1,8 @@ import wretch from 'wretch'; +import safeJsonAddon from './addons/safeJson'; const api = wretch('/mxcube/api/v0.1') + .addon(safeJsonAddon()) .options({ credendials: 'include' }) .headers({ Accept: 'application/json' }); diff --git a/ui/src/api/lims.js b/ui/src/api/lims.js index e5e181be5..e9bef1f99 100644 --- a/ui/src/api/lims.js +++ b/ui/src/api/lims.js @@ -3,11 +3,11 @@ import api from '.'; const endpoint = api.url('/lims'); export function fetchLimsSamples() { - return endpoint.get('/synch_samples').json(); + return endpoint.get('/synch_samples').safeJson(); } export function fetchLimsResults(qid) { - return endpoint.post({ qid }, '/results').json(); + return endpoint.post({ qid }, '/results').safeJson(); } export function sendSelectProposal(number) { diff --git a/ui/src/api/log.js b/ui/src/api/log.js index 8d320ebae..62e1b8def 100644 --- a/ui/src/api/log.js +++ b/ui/src/api/log.js @@ -3,7 +3,7 @@ import api from '.'; const endpoint = api.url('/log'); export function fetchLogMessages() { - return endpoint.get('/').json(); + return endpoint.get('/').safeJson(); } export function sendLogFrontEndTraceBack(stack, state) { diff --git a/ui/src/api/login.js b/ui/src/api/login.js index 24295451c..9c9bc97ed 100644 --- a/ui/src/api/login.js +++ b/ui/src/api/login.js @@ -3,7 +3,7 @@ import api from '.'; const endpoint = api.url('/login'); export function sendLogIn(proposal, password, previousUser) { - return endpoint.post({ proposal, password, previousUser }, '/').json(); + return endpoint.post({ proposal, password, previousUser }, '/').safeJson(); } export function sendSignOut() { @@ -11,7 +11,7 @@ export function sendSignOut() { } export function fetchLoginInfo() { - return endpoint.get('/login_info').json(); + return endpoint.get('/login_info').safeJson(); } export function sendFeedback(sender, content) { diff --git a/ui/src/api/main.js b/ui/src/api/main.js index babb2854e..1a3daeb98 100644 --- a/ui/src/api/main.js +++ b/ui/src/api/main.js @@ -1,9 +1,9 @@ import api from '.'; export function fetchUIProperties() { - return api.get('/uiproperties').json(); + return api.get('/uiproperties').safeJson(); } export function fetchApplicationSettings() { - return api.get('/application_settings').json(); + return api.get('/application_settings').safeJson(); } diff --git a/ui/src/api/queue.js b/ui/src/api/queue.js index 14b821817..bc011ff90 100644 --- a/ui/src/api/queue.js +++ b/ui/src/api/queue.js @@ -3,19 +3,19 @@ import api from '.'; const endpoint = api.url('/queue'); export function fetchQueueState() { - return endpoint.get('/queue_state').json(); + return endpoint.get('/queue_state').safeJson(); } export function fetchAvailableTasks() { - return endpoint.get('/available_tasks').json(); + return endpoint.get('/available_tasks').safeJson(); } export function sendAddQueueItem(items) { - return endpoint.post(items, '/').json(); + return endpoint.post(items, '/').safeJson(); } export function sendUpdateQueueItem(sid, tindex, data) { - return endpoint.post(data, `/${sid}/${tindex}`).json(); + return endpoint.post(data, `/${sid}/${tindex}`).safeJson(); } export function sendDeleteQueueItem(itemPosList) { @@ -61,11 +61,11 @@ export function sendMoveTask(sampleID, oldIndex, newIndex) { } export function sendSetAutoMountSample(automount) { - return endpoint.post({ automount }, '/automount').json(); + return endpoint.post({ automount }, '/automount').safeJson(); } export function sendSetAutoAddDiffPlan(autoadddiffplan) { - return endpoint.post({ autoadddiffplan }, '/auto_add_diffplan').json(); + return endpoint.post({ autoadddiffplan }, '/auto_add_diffplan').safeJson(); } export function sendSetNumSnapshots(numSnapshots) { @@ -73,7 +73,7 @@ export function sendSetNumSnapshots(numSnapshots) { } export function sendSetGroupFolder(path) { - return endpoint.post({ path }, '/group_folder').json(); + return endpoint.post({ path }, '/group_folder').safeJson(); } export function sendSetQueueSettings(name, value) { @@ -83,5 +83,5 @@ export function sendSetQueueSettings(name, value) { export function sendUpdateDependentFields(task_name, field_data) { return endpoint .post({ task_name, field_data }, '/update_dependent_field') - .json(); + .safeJson(); } diff --git a/ui/src/api/remoteAccess.js b/ui/src/api/remoteAccess.js index d46b6a809..467dc8e91 100644 --- a/ui/src/api/remoteAccess.js +++ b/ui/src/api/remoteAccess.js @@ -3,7 +3,7 @@ import api from '.'; const endpoint = api.url('/ra'); export function fetchRemoteAccessState() { - return endpoint.get('/').json(); + return endpoint.get('/').safeJson(); } export function sendUpdateAllowRemote(allow) { @@ -45,7 +45,7 @@ export function sendLogoutUser(username) { } export function fetchChatMessages() { - return endpoint.get('/chat').json(); + return endpoint.get('/chat').safeJson(); } export function sendChatMessage(message, username) { diff --git a/ui/src/api/sampleChanger.js b/ui/src/api/sampleChanger.js index a38a1e57e..572d0ef60 100644 --- a/ui/src/api/sampleChanger.js +++ b/ui/src/api/sampleChanger.js @@ -3,27 +3,27 @@ import api from '.'; const endpoint = api.url('/sample_changer'); export function fetchSampleChangerInitialState() { - return endpoint.get('/get_initial_state').json(); + return endpoint.get('/get_initial_state').safeJson(); } export function fetchSampleChangerContents() { - return endpoint.get('/contents').json(); + return endpoint.get('/contents').safeJson(); } export function fetchLoadedSample() { - return endpoint.get('/loaded_sample').json(); + return endpoint.get('/loaded_sample').safeJson(); } export function fetchSamplesList() { - return endpoint.get('/samples_list').json(); + return endpoint.get('/samples_list').safeJson(); } export function sendSelectContainer(address) { - return endpoint.get(`/select/${address}`).json(); + return endpoint.get(`/select/${address}`).safeJson(); } export function sendScanSampleChanger(address) { - return endpoint.get(`/scan/${address}`).json(); + return endpoint.get(`/scan/${address}`).safeJson(); } export function sendMountSample(sampleData) { @@ -47,5 +47,5 @@ export function sendSampleChangerCommand(cmdparts, args) { } export function sendSyncWithCrims() { - return endpoint.get('/sync_with_crims').json(); + return endpoint.get('/sync_with_crims').safeJson(); } diff --git a/ui/src/api/sampleview.js b/ui/src/api/sampleview.js index f4624a6da..e0edd8c06 100644 --- a/ui/src/api/sampleview.js +++ b/ui/src/api/sampleview.js @@ -3,19 +3,19 @@ import api from '.'; const endpoint = api.url('/sampleview'); export function fetchImageData() { - return endpoint.get('/camera').json(); + return endpoint.get('/camera').safeJson(); } export function sendSetVideoSize(width, height) { - return endpoint.post({ width, height }, '/camera').json(); + return endpoint.post({ width, height }, '/camera').safeJson(); } export function fetchShapes() { - return endpoint.get('/shapes').json(); + return endpoint.get('/shapes').safeJson(); } export function sendAddOrUpdateShapes(shapes) { - return endpoint.post({ shapes }, '/shapes').json(); + return endpoint.post({ shapes }, '/shapes').safeJson(); } export function sendDeleteShape(id) { @@ -31,11 +31,11 @@ export function sendSetCentringMethod(centringMethod) { } export function sendStartClickCentring() { - return endpoint.put(undefined, '/centring/start3click').json(); + return endpoint.put(undefined, '/centring/start3click').safeJson(); } export function sendRecordCentringClick(x, y) { - return endpoint.put({ clickPos: { x, y } }, '/centring/click').json(); + return endpoint.put({ clickPos: { x, y } }, '/centring/click').safeJson(); } export function sendAcceptCentring() { diff --git a/ui/src/api/workflow.js b/ui/src/api/workflow.js index 27d517911..0aadeb03d 100644 --- a/ui/src/api/workflow.js +++ b/ui/src/api/workflow.js @@ -3,7 +3,7 @@ import api from '.'; const endpoint = api.url('/workflow'); export function fetchAvailableWorkflows() { - return endpoint.get('/').json(); + return endpoint.get('/').safeJson(); } export function sendSubmitWorkflowParameters(data) {