From 4e35c283ee577c8d2553753c1921b63195a10fd2 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Sun, 24 May 2020 13:02:46 +0100 Subject: [PATCH 1/3] Add HTTP handler This allows use via API Gateway in a web form. --- index.js | 16 ++++++- src/http.js | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 src/http.js diff --git a/index.js b/index.js index 638e473..8f9b03c 100644 --- a/index.js +++ b/index.js @@ -7,4 +7,18 @@ const bot = probot.create(); bot.load( require( './src' ) ); // Lambda Handler -module.exports.probotHandler = probot.buildHandler( bot ); +const handler = probot.buildHandler( bot ); +module.exports.probotHandler = function ( event, context, callback ) { + switch ( event.path ) { + // Pass check requests to HTTP. + case '/check': + const http = require( './src/http' ); + http( event, context, callback ) + .then( res => callback( null, res ) ) + .catch( err => callback( err ) ); + return; + + default: + return handler( event, context, callback ); + } +}; diff --git a/src/http.js b/src/http.js new file mode 100644 index 0000000..ee35517 --- /dev/null +++ b/src/http.js @@ -0,0 +1,127 @@ +/** + * HTTP handler. + * + * This handler is responsible for handling web requests from lint-check. + */ +const fs = require( 'fs' ); +const path = require( 'path' ); +const pify = require( 'pify' ); + +const prepareLinters = require( './linters' ); +const { combineLinters } = require( './util' ); + +class ClientError extends Error { + statusCode = 400; + code = null; + + constructor( code, message ) { + super( message ); + this.code = code; + } +} + +const CORS_HEADERS = { + 'Access-Control-Allow-Headers': 'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token', + 'Access-Control-Allow-Methods': 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT', + 'Access-Control-Allow-Origin': '*', +}; + +module.exports = async ( event, context ) => { + console.log( event ); + try { + if ( ! event.body ) { + console.log( 'no body' ); + throw new ClientError( 'no_body', 'Missing body.' ); + } + + const data = new URLSearchParams( event.body );; + let filename = data.get( 'filename' ); + + // Set default filename based on type. + if ( ! filename ) { + switch ( data.get( 'type' ) ) { + case 'php': + case 'js': + case 'css': + filename = `file.${ data.get( 'type' ) }`; + break; + + default: + throw new ClientError( 'invalid_type', 'Invalid type.' ); + } + } + + // Sanitize inputs. + const sanitizedFilename = filename.replace( /[^a-z0-9_\-.\/]+/gi, '' ).replace( /\.\//gi, '' ); + if ( sanitizedFilename !== filename ) { + console.warn( 'Attempt to escape root' ); + console.log( filename ); + throw new ClientError( 'no_file', 'Invalid filename.' ); + } + + // First, save the input to a dummy file. + const dir = await pify( fs.mkdtemp )( '/tmp/lint' ); + + // If it's in a subfolder, make those. + const fnParts = sanitizedFilename.split( '/' ); + if ( fnParts.length > 1 ) { + let baseDir = dir; + while ( fnParts.length > 1 ) { + const nextPart = fnParts.shift(); + if ( nextPart === '..' || nextPart === '.' ) { + console.warn( 'Attempt to escape root' ); + console.log( filename ); + throw new ClientError( 'invalid_path', 'Invalid path part' ); + } + + baseDir = path.join( baseDir, nextPart ); + await pify( fs.mkdir )( baseDir ); + } + } + + // Write code to the path. + const res = await pify( fs.writeFile )( path.join( dir, sanitizedFilename ), data.get( 'code' ) ); + console.log( path.join( dir, sanitizedFilename ), res ); + + // Then, prepare the linters. + const config = { + version: data.get( 'version' ) || 'latest', + phpcs: { + enabled: true, + version: 'inherit', + }, + eslint: { + enabled: true, + version: 'inherit', + }, + stylelint: { + enabled: false, + version: 'inherit', + }, + }; + const linters = await prepareLinters( Promise.resolve( config ) ); + + // Run the linters. + const results = await Promise.all( linters.map( linter => linter( dir ) ) ); + + // Combine results. + const combined = combineLinters( results ); + + // return results; + return { + statusCode: 200, + headers: CORS_HEADERS, + body: JSON.stringify( combined ), + }; + } catch ( err ) { + console.log( err ); + return { + statusCode: err.statusCode || 500, + headers: CORS_HEADERS, + body: JSON.stringify( { + error: err.code || 'unknown', + message: err.message || '' + err, + } ), + }; + } +}; \ No newline at end of file From fb3f36763c2ea8ed0abec00342ce0d25779f5ab5 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Wed, 27 May 2020 18:02:08 +0100 Subject: [PATCH 2/3] Clean up logging --- src/http.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/http.js b/src/http.js index ee35517..c93d060 100644 --- a/src/http.js +++ b/src/http.js @@ -30,11 +30,10 @@ module.exports = async ( event, context ) => { console.log( event ); try { if ( ! event.body ) { - console.log( 'no body' ); throw new ClientError( 'no_body', 'Missing body.' ); } - const data = new URLSearchParams( event.body );; + const data = new URLSearchParams( event.body ); let filename = data.get( 'filename' ); // Set default filename based on type. @@ -55,7 +54,7 @@ module.exports = async ( event, context ) => { const sanitizedFilename = filename.replace( /[^a-z0-9_\-.\/]+/gi, '' ).replace( /\.\//gi, '' ); if ( sanitizedFilename !== filename ) { console.warn( 'Attempt to escape root' ); - console.log( filename ); + console.warn( filename ); throw new ClientError( 'no_file', 'Invalid filename.' ); } @@ -70,7 +69,7 @@ module.exports = async ( event, context ) => { const nextPart = fnParts.shift(); if ( nextPart === '..' || nextPart === '.' ) { console.warn( 'Attempt to escape root' ); - console.log( filename ); + console.warn( filename ); throw new ClientError( 'invalid_path', 'Invalid path part' ); } @@ -80,8 +79,8 @@ module.exports = async ( event, context ) => { } // Write code to the path. - const res = await pify( fs.writeFile )( path.join( dir, sanitizedFilename ), data.get( 'code' ) ); console.log( path.join( dir, sanitizedFilename ), res ); + const res = await pify( fs.writeFile )( path.join( dir, sanitizedFilename ), data.get( 'code' ) ); // Then, prepare the linters. const config = { From 0a19c906a73d6a5a7bb79064bd9da3f1108a584d Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Fri, 29 May 2020 17:28:05 +0100 Subject: [PATCH 3/3] Use default config from main config file --- src/config.js | 2 ++ src/http.js | 14 ++------------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/config.js b/src/config.js index effdf46..cc28c22 100644 --- a/src/config.js +++ b/src/config.js @@ -51,3 +51,5 @@ module.exports = async ( context, head ) => { } } }; + +module.exports.DEFAULT_CONFIG = DEFAULT_CONFIG; diff --git a/src/http.js b/src/http.js index c93d060..bd1d46b 100644 --- a/src/http.js +++ b/src/http.js @@ -7,6 +7,7 @@ const fs = require( 'fs' ); const path = require( 'path' ); const pify = require( 'pify' ); +const { DEFAULT_CONFIG } = require( './config' ); const prepareLinters = require( './linters' ); const { combineLinters } = require( './util' ); @@ -84,19 +85,8 @@ module.exports = async ( event, context ) => { // Then, prepare the linters. const config = { + ...DEFAULT_CONFIG, version: data.get( 'version' ) || 'latest', - phpcs: { - enabled: true, - version: 'inherit', - }, - eslint: { - enabled: true, - version: 'inherit', - }, - stylelint: { - enabled: false, - version: 'inherit', - }, }; const linters = await prepareLinters( Promise.resolve( config ) );