blob: 0301aa8060b4ddbb9eca49a87fda6bf5e1af3803 [file] [log] [blame] [raw]
Matt Godboltf68198a2020-09-26 17:50:40 -05001// Copyright (c) 2017, Compiler Explorer Authors
Matt Godbolt1c3e94d2017-12-11 20:05:58 -06002// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// * Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9// * Redistributions in binary form must reproduce the above copyright
10// notice, this list of conditions and the following disclaimer in the
11// documentation and/or other materials provided with the distribution.
12//
13// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
17// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23// POSSIBILITY OF SUCH DAMAGE.
24
Austin Morton044dcfb2020-09-26 16:59:26 -040025import bodyParser from 'body-parser';
26import express from 'express';
27import _ from 'underscore';
Matt Godbolt1c3e94d2017-12-11 20:05:58 -060028
Matt Godbolt21b764f2022-04-25 18:22:11 -050029import {ClientStateNormalizer} from '../clientstate-normalizer';
30import {logger} from '../logger';
31import {getShortenerTypeByKey} from '../shortener';
Austin Morton044dcfb2020-09-26 16:59:26 -040032import * as utils from '../utils';
33
Matt Godbolt21b764f2022-04-25 18:22:11 -050034import {withAssemblyDocumentationProviders} from './assembly-documentation';
35import {FormattingHandler} from './formatting';
Jeremy Rifkin4c2fe0a2022-06-09 16:52:45 -040036import {getSiteTemplates} from './site-templates';
Austin Morton044dcfb2020-09-26 16:59:26 -040037
Rubén Rincón Blancof19bebc2022-05-06 18:56:33 +020038function methodNotAllowed(req, res) {
39 res.send('Method Not Allowed');
40 return res.status(405).end();
41}
42
Austin Morton044dcfb2020-09-26 16:59:26 -040043export class ApiHandler {
Matt Godbolt17b2bb82021-02-15 12:52:46 -060044 constructor(compileHandler, ceProps, storageHandler, urlShortenService) {
Matt Godbolt1c3e94d2017-12-11 20:05:58 -060045 this.compilers = [];
partouf2ef40c92018-01-01 22:30:26 +010046 this.languages = [];
RabsRincon84298422018-04-08 03:05:38 +020047 this.usedLangIds = [];
Partouf8bb5e7d2018-10-15 02:32:31 +020048 this.options = null;
Partouf652fd6b2018-10-01 20:03:51 +020049 this.storageHandler = storageHandler;
Mats Larsend7c4eec2021-12-07 10:43:08 +000050 /** @type {e.Router} */
Matt Godbolt1c3e94d2017-12-11 20:05:58 -060051 this.handle = express.Router();
Rubén Rincón Blancoccff4b92020-08-04 22:39:02 +020052 const cacheHeader = `public, max-age=${ceProps('apiMaxAgeSecs', 24 * 60 * 60)}`;
Matt Godbolt1c3e94d2017-12-11 20:05:58 -060053 this.handle.use((req, res, next) => {
Matt Godbolt21b764f2022-04-25 18:22:11 -050054 res.header({
55 'Access-Control-Allow-Origin': '*',
56 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept',
57 'Cache-Control': cacheHeader,
58 });
Matt Godbolt1c3e94d2017-12-11 20:05:58 -060059 next();
60 });
Rubén Rincón Blancof19bebc2022-05-06 18:56:33 +020061 this.handle.route('/compilers').get(this.handleCompilers.bind(this)).all(methodNotAllowed);
62
63 this.handle.route('/compilers/:language').get(this.handleCompilers.bind(this)).all(methodNotAllowed);
64
65 this.handle.route('/languages').get(this.handleLanguages.bind(this)).all(methodNotAllowed);
66
67 this.handle.route('/libraries/:language').get(this.handleLangLibraries.bind(this)).all(methodNotAllowed);
68
69 this.handle.route('/libraries').get(this.handleAllLibraries.bind(this)).all(methodNotAllowed);
RabsRinconb8c8c652018-04-20 22:07:40 +020070
Mats Larsend7c4eec2021-12-07 10:43:08 +000071 // Binding for assembly documentation
Mats Larsen9b890b12022-01-31 14:05:21 +010072 withAssemblyDocumentationProviders(this.handle);
Matt Godbolt273017c2021-03-14 12:39:55 -050073 // Legacy binding for old clients.
Rubén Rincón Blancof19bebc2022-05-06 18:56:33 +020074 this.handle
75 .route('/asm/:opcode')
76 .get((req, res) => res.redirect(`amd64/${req.params.opcode}`))
77 .all(methodNotAllowed);
Partouf865d2172018-10-26 13:33:42 +020078
Patrick Quist0f01b992020-09-05 12:22:44 +020079 const maxUploadSize = ceProps('maxUploadSize', '1mb');
80 const textParser = bodyParser.text({limit: ceProps('bodyParserLimit', maxUploadSize), type: () => true});
81
Rubén Rincón Blancof19bebc2022-05-06 18:56:33 +020082 this.handle
83 .route('/compiler/:compiler/compile')
84 .post(textParser, compileHandler.handle.bind(compileHandler))
85 .all(methodNotAllowed);
86 this.handle
87 .route('/compiler/:compiler/cmake')
88 .post(compileHandler.handleCmake.bind(compileHandler))
89 .all(methodNotAllowed);
Partouf69a43f72019-08-13 22:15:53 +020090
Rubén Rincón Blancof19bebc2022-05-06 18:56:33 +020091 this.handle
92 .route('/popularArguments/:compiler')
93 .post(compileHandler.handlePopularArguments.bind(compileHandler))
Rubén Rincón Blancof19bebc2022-05-06 18:56:33 +020094 .get(compileHandler.handlePopularArguments.bind(compileHandler))
95 .all(methodNotAllowed);
96 this.handle
97 .route('/optimizationArguments/:compiler')
Rubén Rincón Blanco16dca9d2022-08-25 21:51:10 +020098 .post(compileHandler.handleOptimizationArguments.bind(compileHandler))
Rubén Rincón Blancof19bebc2022-05-06 18:56:33 +020099 .get(compileHandler.handleOptimizationArguments.bind(compileHandler))
100 .all(methodNotAllowed);
RabsRinconb8c8c652018-04-20 22:07:40 +0200101
Mats Larsen53b9e0a2021-08-25 00:46:38 +0100102 const formatHandler = new FormattingHandler(ceProps);
Rubén Rincón Blancof19bebc2022-05-06 18:56:33 +0200103 this.handle
104 .route('/format/:tool')
105 .post((req, res) => formatHandler.handle(req, res))
106 .all(methodNotAllowed);
107 this.handle
108 .route('/formats')
109 .get((req, res) => {
110 const all = Object.values(formatHandler.formatters).map(formatter => formatter.formatterInfo);
111 res.send(all);
112 })
113 .all(methodNotAllowed);
Jeremy Rifkin4c2fe0a2022-06-09 16:52:45 -0400114 this.handle
115 .route('/siteTemplates')
116 .get((req, res) => {
117 res.send(getSiteTemplates());
118 })
119 .all(methodNotAllowed);
Partouf652fd6b2018-10-01 20:03:51 +0200120
Rubén Rincón Blancof19bebc2022-05-06 18:56:33 +0200121 this.handle.route('/shortlinkinfo/:id').get(this.shortlinkInfoHandler.bind(this)).all(methodNotAllowed);
Matt Godbolt17b2bb82021-02-15 12:52:46 -0600122
123 const shortenerType = getShortenerTypeByKey(urlShortenService);
124 this.shortener = new shortenerType(storageHandler);
Rubén Rincón Blancof19bebc2022-05-06 18:56:33 +0200125 this.handle.route('/shortener').post(this.shortener.handle.bind(this.shortener)).all(methodNotAllowed);
RabsRincon16ff1a82021-08-13 05:31:40 +0200126
127 this.release = {
128 gitReleaseName: '',
129 releaseBuildNumber: '',
130 };
Rubén Rincón Blancof19bebc2022-05-06 18:56:33 +0200131 this.handle.route('/version').get(this.handleReleaseName.bind(this)).all(methodNotAllowed);
132 this.handle.route('/releaseBuild').get(this.handleReleaseBuild.bind(this)).all(methodNotAllowed);
Partouf652fd6b2018-10-01 20:03:51 +0200133 }
134
135 shortlinkInfoHandler(req, res, next) {
136 const id = req.params.id;
Matt Godbolt21b764f2022-04-25 18:22:11 -0500137 this.storageHandler
138 .expandId(id)
Partouf652fd6b2018-10-01 20:03:51 +0200139 .then(result => {
140 const config = JSON.parse(result.config);
141
142 if (config.content) {
Austin Morton044dcfb2020-09-26 16:59:26 -0400143 const normalizer = new ClientStateNormalizer();
Partouf652fd6b2018-10-01 20:03:51 +0200144 normalizer.fromGoldenLayout(config);
145
Matt Godbolt4cf57922020-01-12 11:18:39 -0600146 res.send(normalizer.normalized);
Partouf652fd6b2018-10-01 20:03:51 +0200147 } else {
Matt Godbolt4cf57922020-01-12 11:18:39 -0600148 res.send(config);
Partouf652fd6b2018-10-01 20:03:51 +0200149 }
150 })
151 .catch(err => {
152 logger.warn(`Exception thrown when expanding ${id}: `, err);
153 next({
154 statusCode: 404,
Rubén Rincón Blancoccff4b92020-08-04 22:39:02 +0200155 message: `ID "${id}" could not be found`,
Partouf652fd6b2018-10-01 20:03:51 +0200156 });
157 });
Matt Godbolt1c3e94d2017-12-11 20:05:58 -0600158 }
159
partouf2ef40c92018-01-01 22:30:26 +0100160 handleLanguages(req, res) {
Partouf8bb5e7d2018-10-15 02:32:31 +0200161 const availableLanguages = this.usedLangIds.map(val => {
162 let lang = this.languages[val];
163 let newLangObj = Object.assign({}, lang);
164 if (this.options) {
165 newLangObj.defaultCompiler = this.options.options.defaultCompiler[lang.id];
166 }
167 return newLangObj;
168 });
partouf2ef40c92018-01-01 22:30:26 +0100169
Matt Godbolt4cf57922020-01-12 11:18:39 -0600170 this.outputList(availableLanguages, 'Id', req, res);
171 }
172
Patrick Quistab2f8e22020-09-02 21:36:02 +0200173 filterCompilerProperties(list, selectedFields) {
Matt Godbolt21b764f2022-04-25 18:22:11 -0500174 return _.map(list, compiler => {
Partouf6b6a9ef2020-09-03 00:03:25 +0200175 return _.pick(compiler, selectedFields);
Patrick Quistab2f8e22020-09-02 21:36:02 +0200176 });
177 }
178
Matt Godbolt4cf57922020-01-12 11:18:39 -0600179 outputList(list, title, req, res) {
partouf2ef40c92018-01-01 22:30:26 +0100180 if (req.accepts(['text', 'json']) === 'json') {
Patrick Quistab2f8e22020-09-02 21:36:02 +0200181 if (req.query.fields === 'all') {
182 res.send(list);
183 } else {
Matt Godbolt21b764f2022-04-25 18:22:11 -0500184 const defaultfields = [
185 'id',
186 'name',
187 'lang',
188 'compilerType',
189 'semver',
190 'extensions',
191 'monaco',
192 'instructionSet',
193 ];
Patrick Quistab2f8e22020-09-02 21:36:02 +0200194 if (req.query.fields) {
195 const filteredList = this.filterCompilerProperties(list, req.query.fields.split(','));
196 res.send(filteredList);
197 } else {
198 const filteredList = this.filterCompilerProperties(list, defaultfields);
199 res.send(filteredList);
200 }
201 }
Matt Godbolt4cf57922020-01-12 11:18:39 -0600202 return;
Partouf8bb5e7d2018-10-15 02:32:31 +0200203 }
Patrick Quistab2f8e22020-09-02 21:36:02 +0200204
Matt Godbolt4cf57922020-01-12 11:18:39 -0600205 const maxLength = _.max(_.pluck(_.pluck(list, 'id').concat([title]), 'length'));
206 res.set('Content-Type', 'text/plain');
Matt Godbolt21b764f2022-04-25 18:22:11 -0500207 res.send(
208 utils.padRight(title, maxLength) +
209 ' | Name\n' +
RabsRincon40fe7692022-04-26 05:26:48 +0200210 list.map(lang => utils.padRight(lang.id, maxLength) + ' | ' + lang.name).join('\n'),
Matt Godbolt21b764f2022-04-25 18:22:11 -0500211 );
Partouf8bb5e7d2018-10-15 02:32:31 +0200212 }
213
Partouf7ba94cc2018-10-15 02:39:11 +0200214 getLibrariesAsArray(languageId) {
215 const libsForLanguageObj = this.options.options.libs[languageId];
Partouf55560382018-10-15 02:36:39 +0200216 if (!libsForLanguageObj) return [];
217
Matt Godbolt21b764f2022-04-25 18:22:11 -0500218 return Object.keys(libsForLanguageObj).map(key => {
Partouf7ba94cc2018-10-15 02:39:11 +0200219 const language = libsForLanguageObj[key];
Matt Godbolt21b764f2022-04-25 18:22:11 -0500220 const versionArr = Object.keys(language.versions).map(key => {
Partouf7ba94cc2018-10-15 02:39:11 +0200221 let versionObj = Object.assign({}, language.versions[key]);
Partouf55560382018-10-15 02:36:39 +0200222 versionObj.id = key;
223 return versionObj;
224 });
225
226 return {
227 id: key,
Partouf7ba94cc2018-10-15 02:39:11 +0200228 name: language.name,
229 description: language.description,
230 url: language.url,
Rubén Rincón Blancoccff4b92020-08-04 22:39:02 +0200231 versions: versionArr,
Partouf55560382018-10-15 02:36:39 +0200232 };
233 });
234 }
235
RabsRincona4923112019-01-28 08:22:54 +0100236 handleLangLibraries(req, res, next) {
Partouf8bb5e7d2018-10-15 02:32:31 +0200237 if (this.options) {
238 if (req.params.language) {
Matt Godbolt4cf57922020-01-12 11:18:39 -0600239 res.send(this.getLibrariesAsArray(req.params.language));
Partouf8bb5e7d2018-10-15 02:32:31 +0200240 } else {
241 next({
242 statusCode: 404,
Rubén Rincón Blancoccff4b92020-08-04 22:39:02 +0200243 message: 'Language is required',
Partouf8bb5e7d2018-10-15 02:32:31 +0200244 });
245 }
246 } else {
247 next({
248 statusCode: 500,
Rubén Rincón Blancoccff4b92020-08-04 22:39:02 +0200249 message: 'Internal error',
Partouf8bb5e7d2018-10-15 02:32:31 +0200250 });
partouf2ef40c92018-01-01 22:30:26 +0100251 }
252 }
253
RabsRincona4923112019-01-28 08:22:54 +0100254 handleAllLibraries(req, res, next) {
255 if (this.options) {
Matt Godbolt4cf57922020-01-12 11:18:39 -0600256 res.send(this.options.options.libs);
RabsRincona4923112019-01-28 08:22:54 +0100257 } else {
258 next({
259 statusCode: 500,
Rubén Rincón Blancoccff4b92020-08-04 22:39:02 +0200260 message: 'Internal error',
RabsRincona4923112019-01-28 08:22:54 +0100261 });
262 }
263 }
264
partouf2ef40c92018-01-01 22:30:26 +0100265 handleCompilers(req, res) {
266 let filteredCompilers = this.compilers;
267 if (req.params.language) {
Matt Godbolt21b764f2022-04-25 18:22:11 -0500268 filteredCompilers = this.compilers.filter(val => val.lang === req.params.language);
partouf2ef40c92018-01-01 22:30:26 +0100269 }
270
Matt Godbolt4cf57922020-01-12 11:18:39 -0600271 this.outputList(filteredCompilers, 'Compiler Name', req, res);
partouf2ef40c92018-01-01 22:30:26 +0100272 }
273
RabsRincon16ff1a82021-08-13 05:31:40 +0200274 handleReleaseName(req, res) {
275 res.send(this.release.gitReleaseName);
276 }
277
278 handleReleaseBuild(req, res) {
279 res.send(this.release.releaseBuildNumber);
280 }
281
Matt Godbolt1c3e94d2017-12-11 20:05:58 -0600282 setCompilers(compilers) {
283 this.compilers = compilers;
RabsRincon84298422018-04-08 03:05:38 +0200284 this.usedLangIds = _.uniq(_.pluck(this.compilers, 'lang'));
Matt Godbolt1c3e94d2017-12-11 20:05:58 -0600285 }
partouf2ef40c92018-01-01 22:30:26 +0100286
287 setLanguages(languages) {
288 this.languages = languages;
289 }
Partouf8bb5e7d2018-10-15 02:32:31 +0200290
291 setOptions(options) {
292 this.options = options;
293 }
RabsRincon16ff1a82021-08-13 05:31:40 +0200294
295 setReleaseInfo(gitReleaseName, releaseBuildNumber) {
296 this.release = {
297 gitReleaseName: gitReleaseName || '',
298 releaseBuildNumber: releaseBuildNumber || '',
299 };
300 }
Matt Godbolt1c3e94d2017-12-11 20:05:58 -0600301}