From f6f1319c79872e416b0db0fb42405eac160fffe4 Mon Sep 17 00:00:00 2001 From: Marcel Kloubert Date: Sun, 4 Jun 2017 15:04:29 +0200 Subject: [PATCH] password prompts for HTTP targets --- CHANGELOG.md | 2 +- package.json | 10 ++ src/plugins/http.ts | 411 ++++++++++++++++++++++++-------------------- 3 files changed, 237 insertions(+), 186 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4153f34..a737154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 9.16.0 (June 4th, 2017; password prompts) -* password box is shown now if no password / token / key is defined in [API](https://github.com/mkloubert/vs-deploy/wiki/target_api), [Azure blob](https://github.com/mkloubert/vs-deploy/wiki/target_azureblob), [DropBox](https://github.com/mkloubert/vs-deploy/wiki/target_dropbox), [FTP](https://github.com/mkloubert/vs-deploy/wiki/target_ftp) and [mail](https://github.com/mkloubert/vs-deploy/wiki/target_mail) targets +* password box is shown now if no password / token / key is defined in [API](https://github.com/mkloubert/vs-deploy/wiki/target_api), [Azure blob](https://github.com/mkloubert/vs-deploy/wiki/target_azureblob), [DropBox](https://github.com/mkloubert/vs-deploy/wiki/target_dropbox), [FTP](https://github.com/mkloubert/vs-deploy/wiki/target_ftp), [HTTP](https://github.com/mkloubert/vs-deploy/wiki/target_http) and [mail](https://github.com/mkloubert/vs-deploy/wiki/target_mail) targets ## 9.15.1 (June 4th, 2017; bugfixes and password prompt for SFTP) diff --git a/package.json b/package.json index adc2628..1b7e7d4 100644 --- a/package.json +++ b/package.json @@ -11242,6 +11242,11 @@ "description": "Submit the content of a file or not.", "default": true }, + "promptForPassword": { + "type": "boolean", + "description": "Prompt for a password if not defined.", + "default": true + }, "description": { "type": "string", "description": "A description for the target." @@ -15514,6 +15519,11 @@ "transformerOptions": { "description": "Optional data for the transformer script." }, + "promptForPassword": { + "type": "boolean", + "description": "Prompt for a password if not defined.", + "default": true + }, "description": { "type": "string", "description": "A description for the target." diff --git a/src/plugins/http.ts b/src/plugins/http.ts index 236b061..c051932 100644 --- a/src/plugins/http.ts +++ b/src/plugins/http.ts @@ -35,6 +35,7 @@ const MIME = require('mime'); import * as Moment from 'moment'; import * as Path from 'path'; import * as URL from 'url'; +import * as vscode from 'vscode'; const DATE_RFC2822_UTC = "ddd, DD MMM YYYY HH:mm:ss [GMT]"; @@ -86,11 +87,11 @@ class HttpPlugin extends deploy_objects.DeployPluginBase { let me = this; - let hasCanceled = false; + let hasCancelled = false; let completed = (err?: any) => { if (opts.onCompleted) { opts.onCompleted(me, { - canceled: hasCanceled, + canceled: hasCancelled, error: err, file: file, target: target, @@ -98,9 +99,9 @@ class HttpPlugin extends deploy_objects.DeployPluginBase { } }; - me.onCancelling(() => hasCanceled = true, opts); + me.onCancelling(() => hasCancelled = true, opts); - if (hasCanceled) { + if (hasCancelled) { completed(); // cancellation requested } else { @@ -126,12 +127,12 @@ class HttpPlugin extends deploy_objects.DeployPluginBase { } let user = deploy_helpers.toStringSafe(target.user); - if (user) { - //TODO: password prompt - let pwd = deploy_helpers.toStringSafe(target.password); - - headers['Authorization'] = 'Basic ' + - (new Buffer(`${user}:${pwd}`)).toString('base64'); + let pwd: string; + if ('' !== user) { + pwd = deploy_helpers.toStringSafe(target.password); + if ('' === pwd) { + pwd = undefined; + } } let submitFileHeader = deploy_helpers.toBooleanSafe(target.submitFileHeader, false); @@ -150,210 +151,250 @@ class HttpPlugin extends deploy_objects.DeployPluginBase { }); } - // get file info - FS.lstat(file, (err, stats) => { - if (err) { - completed(err); - return; + let startRequest = () => { + if ('' !== user) { + headers['Authorization'] = 'Basic ' + + (new Buffer(`${user}:${pwd}`)).toString('base64'); } - let creationTime = Moment(stats.birthtime).utc(); - let lastWriteTime = Moment(stats.mtime).utc(); - - // read file - FS.readFile(file, (err, untransformedData) => { + // get file info + FS.lstat(file, (err, stats) => { if (err) { completed(err); return; } - try { - let subCtx: DataTransformerContext = { - globals: me.context.globals(), - file: file, - remoteFile: relativePath, - url: url, - }; - - let tCtx = me.createDataTransformerContext(target, deploy_contracts.DataTransformerMode.Transform, - subCtx); - tCtx.data = untransformedData; - - let tResult = me.loadDataTransformer(target, deploy_contracts.DataTransformerMode.Transform)(tCtx); - Promise.resolve(tResult).then((dataToSend) => { - try { - let parsePlaceHolders = (str: string, transformer: (val: any) => string): string => { - let values = deploy_values.getBuildInValues().map(x => { - let sv = new deploy_values.StaticValue({ - name: x.name, - value: transformer(x.value), + let creationTime = Moment(stats.birthtime).utc(); + let lastWriteTime = Moment(stats.mtime).utc(); + + // read file + FS.readFile(file, (err, untransformedData) => { + if (err) { + completed(err); + return; + } + + try { + let subCtx: DataTransformerContext = { + globals: me.context.globals(), + file: file, + remoteFile: relativePath, + url: url, + }; + + let tCtx = me.createDataTransformerContext(target, deploy_contracts.DataTransformerMode.Transform, + subCtx); + tCtx.data = untransformedData; + + let tResult = me.loadDataTransformer(target, deploy_contracts.DataTransformerMode.Transform)(tCtx); + Promise.resolve(tResult).then((dataToSend) => { + try { + let parsePlaceHolders = (str: string, transformer: (val: any) => string): string => { + let values = deploy_values.getBuildInValues().map(x => { + let sv = new deploy_values.StaticValue({ + name: x.name, + value: transformer(x.value), + }); + sv.id = x.id; + + return sv; }); - sv.id = x.id; - - return sv; - }); - - values.push(new deploy_values.StaticValue({ - name: 'VSDeploy-Date', - value: transformer(now.format(DATE_RFC2822_UTC)) - })); - - values.push(new deploy_values.StaticValue({ - name: 'VSDeploy-File', - value: transformer(relativePath) - })); - - values.push(new deploy_values.StaticValue({ - name: 'VSDeploy-File-Mime', - value: transformer(contentType) - })); - - let basename = Path.basename(file); - values.push(new deploy_values.StaticValue({ - name: 'VSDeploy-File-Name', - value: transformer(basename) - })); - - let extname = Path.extname(file); - let rootname = basename.substr(0, basename.length - extname.length); - values.push(new deploy_values.StaticValue({ - name: 'VSDeploy-File-Root', - value: transformer(rootname) - })); - - values.push(new deploy_values.StaticValue({ - name: 'VSDeploy-File-Size', - value: transformer(dataToSend.length) - })); - - values.push(new deploy_values.StaticValue({ - name: 'VSDeploy-File-Time-Changed', - value: transformer(lastWriteTime.format(DATE_RFC2822_UTC)) - })); - - values.push(new deploy_values.StaticValue({ - name: 'VSDeploy-File-Time-Created', - value: transformer(lastWriteTime.format(DATE_RFC2822_UTC)) - })); - - str = deploy_values.replaceWithValues(values, str); - - return deploy_helpers.toStringSafe(str); - }; - - let encodeUrlValues = deploy_helpers.toBooleanSafe(target.encodeUrlValues, true); - let targetUrl = URL.parse(parsePlaceHolders(url, encodeUrlValues ? encodeURIComponent : deploy_helpers.toStringSafe)); - - let submitFile = deploy_helpers.toBooleanSafe(target.submitFile, true); - - let submitContentLength = deploy_helpers.toBooleanSafe(target.submitContentLength, true); - if (submitFile && submitContentLength) { - headers['Content-length'] = deploy_helpers.toStringSafe(dataToSend.length, '0'); - } - - let submitContentType = deploy_helpers.toBooleanSafe(target.submitContentType, true); - if (submitFile && submitContentType) { - headers['Content-type'] = contentType; - } - - let submitDate = deploy_helpers.toBooleanSafe(target.submitDate, true); - if (submitDate) { - headers['Date'] = now.format(DATE_RFC2822_UTC); // RFC 2822 - } - let headersToSubmit = {}; - for (let p in headers) { - headersToSubmit[p] = parsePlaceHolders(headers[p], deploy_helpers.toStringSafe); - } - - let protocol = deploy_helpers.toStringSafe(targetUrl.protocol).toLowerCase().trim(); - if ('' === protocol) { - protocol = 'http:'; - } + values.push(new deploy_values.StaticValue({ + name: 'VSDeploy-Date', + value: transformer(now.format(DATE_RFC2822_UTC)) + })); + + values.push(new deploy_values.StaticValue({ + name: 'VSDeploy-File', + value: transformer(relativePath) + })); + + values.push(new deploy_values.StaticValue({ + name: 'VSDeploy-File-Mime', + value: transformer(contentType) + })); + + let basename = Path.basename(file); + values.push(new deploy_values.StaticValue({ + name: 'VSDeploy-File-Name', + value: transformer(basename) + })); + + let extname = Path.extname(file); + let rootname = basename.substr(0, basename.length - extname.length); + values.push(new deploy_values.StaticValue({ + name: 'VSDeploy-File-Root', + value: transformer(rootname) + })); + + values.push(new deploy_values.StaticValue({ + name: 'VSDeploy-File-Size', + value: transformer(dataToSend.length) + })); + + values.push(new deploy_values.StaticValue({ + name: 'VSDeploy-File-Time-Changed', + value: transformer(lastWriteTime.format(DATE_RFC2822_UTC)) + })); + + values.push(new deploy_values.StaticValue({ + name: 'VSDeploy-File-Time-Created', + value: transformer(lastWriteTime.format(DATE_RFC2822_UTC)) + })); + + str = deploy_values.replaceWithValues(values, str); + + return deploy_helpers.toStringSafe(str); + }; + + let encodeUrlValues = deploy_helpers.toBooleanSafe(target.encodeUrlValues, true); + let targetUrl = URL.parse(parsePlaceHolders(url, encodeUrlValues ? encodeURIComponent : deploy_helpers.toStringSafe)); + + let submitFile = deploy_helpers.toBooleanSafe(target.submitFile, true); + + let submitContentLength = deploy_helpers.toBooleanSafe(target.submitContentLength, true); + if (submitFile && submitContentLength) { + headers['Content-length'] = deploy_helpers.toStringSafe(dataToSend.length, '0'); + } - let httpModule: any; - switch (protocol) { - case 'http:': - httpModule = HTTP; - break; + let submitContentType = deploy_helpers.toBooleanSafe(target.submitContentType, true); + if (submitFile && submitContentType) { + headers['Content-type'] = contentType; + } - case 'https:': - httpModule = HTTPs; - break; - } + let submitDate = deploy_helpers.toBooleanSafe(target.submitDate, true); + if (submitDate) { + headers['Date'] = now.format(DATE_RFC2822_UTC); // RFC 2822 + } - if (!httpModule) { - completed(new Error(i18.t('plugins.http.protocolNotSupported', protocol))); - return; - } + let headersToSubmit = {}; + for (let p in headers) { + headersToSubmit[p] = parsePlaceHolders(headers[p], deploy_helpers.toStringSafe); + } - let hostName = deploy_helpers.toStringSafe(targetUrl.hostname).toLowerCase().trim(); - if (!hostName) { - hostName = 'localhost'; - } + let protocol = deploy_helpers.toStringSafe(targetUrl.protocol).toLowerCase().trim(); + if ('' === protocol) { + protocol = 'http:'; + } - let port = deploy_helpers.toStringSafe(targetUrl.port).trim(); - if ('' === port) { - port = 'http:' === protocol ? '80' : '443'; - } + let httpModule: any; + switch (protocol) { + case 'http:': + httpModule = HTTP; + break; - // start the request - let req = httpModule.request({ - headers: headersToSubmit, - host: hostName, - method: method, - path: targetUrl.path, - port: parseInt(port), - protocol: protocol, - }, (resp) => { - if (resp.statusCode > 399 && resp.statusCode < 500) { - completed(new Error(`Client error: [${resp.statusCode}] '${resp.statusMessage}'`)); - return; + case 'https:': + httpModule = HTTPs; + break; } - if (resp.statusCode > 499 && resp.statusCode < 600) { - completed(new Error(`Server error: [${resp.statusCode}] '${resp.statusMessage}'`)); + if (!httpModule) { + completed(new Error(i18.t('plugins.http.protocolNotSupported', protocol))); return; } - if (resp.statusCode > 599) { - completed(new Error(`Error: [${resp.statusCode}] '${resp.statusMessage}'`)); - return; + let hostName = deploy_helpers.toStringSafe(targetUrl.hostname).toLowerCase().trim(); + if (!hostName) { + hostName = 'localhost'; } - if (!(resp.statusCode > 199 && resp.statusCode < 300)) { - completed(new Error(`No success: [${resp.statusCode}] '${resp.statusMessage}'`)); - return; + let port = deploy_helpers.toStringSafe(targetUrl.port).trim(); + if ('' === port) { + port = 'http:' === protocol ? '80' : '443'; } - completed(); - }); + // start the request + let req = httpModule.request({ + headers: headersToSubmit, + host: hostName, + method: method, + path: targetUrl.path, + port: parseInt(port), + protocol: protocol, + }, (resp) => { + if (resp.statusCode > 399 && resp.statusCode < 500) { + completed(new Error(`Client error: [${resp.statusCode}] '${resp.statusMessage}'`)); + return; + } + + if (resp.statusCode > 499 && resp.statusCode < 600) { + completed(new Error(`Server error: [${resp.statusCode}] '${resp.statusMessage}'`)); + return; + } + + if (resp.statusCode > 599) { + completed(new Error(`Error: [${resp.statusCode}] '${resp.statusMessage}'`)); + return; + } + + if (!(resp.statusCode > 199 && resp.statusCode < 300)) { + completed(new Error(`No success: [${resp.statusCode}] '${resp.statusMessage}'`)); + return; + } + + completed(); + }); - req.once('error', (err) => { - if (err) { - completed(err); + req.once('error', (err) => { + if (err) { + completed(err); + } + }); + + if (submitFile) { + // send file content + req.write(dataToSend); } - }); - if (submitFile) { - // send file content - req.write(dataToSend); + req.end(); } - - req.end(); - } - catch (e) { - completed(e); - } - }).catch((err) => { - completed(err); - }); - } - catch (e) { - completed(e); - } + catch (e) { + completed(e); + } + }).catch((err) => { + completed(err); + }); + } + catch (e) { + completed(e); + } + }); }); - }); + }; + + let askForPasswordIfNeeded = () => { + let showPasswordPrompt = false; + if (!deploy_helpers.isEmptyString(user) && deploy_helpers.isNullOrUndefined(pwd)) { + // user defined, but no password + showPasswordPrompt = deploy_helpers.toBooleanSafe(target.promptForPassword, true); + } + + if (showPasswordPrompt) { + vscode.window.showInputBox({ + placeHolder: i18.t('prompts.inputPassword'), + password: true, + }).then((passwordFromUser) => { + if ('undefined' === typeof passwordFromUser) { + hasCancelled = true; + + completed(null); // cancelled + } + else { + pwd = passwordFromUser; + + startRequest(); + } + }, (err) => { + completed(err); + }); + } + else { + startRequest(); + } + }; + + askForPasswordIfNeeded(); } catch (e) { completed(e);