diff --git a/app/common/constants.js b/app/common/constants.js
index 59d5a8aee7..8054a523c8 100644
--- a/app/common/constants.js
+++ b/app/common/constants.js
@@ -116,22 +116,19 @@ export const CONTENT_TYPE_XML = 'application/xml';
export const CONTENT_TYPE_TEXT = 'text/plain';
export const CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded';
export const CONTENT_TYPE_FORM_DATA = 'multipart/form-data';
-export const CONTENT_TYPE_OTHER = '';
+export const CONTENT_TYPE_FILE = 'application/octet-stream';
+export const CONTENT_TYPE_RAW = '';
export const contentTypesMap = {
[CONTENT_TYPE_JSON]: 'JSON',
[CONTENT_TYPE_XML]: 'XML',
// [CONTENT_TYPE_FORM_DATA]: 'Form Data',
- [CONTENT_TYPE_FORM_URLENCODED]: 'Url Encoded',
+ [CONTENT_TYPE_FORM_URLENCODED]: 'Form Url Encoded',
[CONTENT_TYPE_TEXT]: 'Plain Text',
- [CONTENT_TYPE_OTHER]: 'Other',
+ [CONTENT_TYPE_FILE]: 'File Upload',
+ [CONTENT_TYPE_RAW]: 'Raw Body',
};
-export const BODY_TYPE_RAW = 'raw';
-export const BODY_TYPE_FILE = 'file';
-export const BODY_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded';
-export const BODY_TYPE_FORM = 'multipart/form-data';
-
/**
* Get the friendly name for a given content type
*
@@ -139,7 +136,7 @@ export const BODY_TYPE_FORM = 'multipart/form-data';
* @returns {*|string}
*/
export function getContentTypeName (contentType) {
- return contentTypesMap[contentType] || contentTypesMap[CONTENT_TYPE_OTHER];
+ return contentTypesMap[contentType] || 'Other';
}
export function getContentTypeFromHeaders (headers) {
diff --git a/app/common/har.js b/app/common/har.js
index 00ef839a2f..ff189a0e3a 100644
--- a/app/common/har.js
+++ b/app/common/har.js
@@ -1,8 +1,11 @@
+import fs from 'fs';
import * as models from '../models';
import {getRenderedRequest} from './render';
import {jarFromCookies} from './cookies';
import * as util from './misc';
import * as misc from './misc';
+import {CONTENT_TYPE_FILE} from './constants';
+import {newBodyRaw} from '../models/request';
export function exportHarWithRequest (renderedRequest, addContentLength = false) {
if (addContentLength) {
@@ -18,8 +21,17 @@ export function exportHarWithRequest (renderedRequest, addContentLength = false)
}
}
- // Luckily, Insomnia uses the same body format as HAR :)
- const postData = renderedRequest.body;
+ let postData = '';
+ if (renderedRequest.body.fileName) {
+ try {
+ postData = newBodyRaw(fs.readFileSync(renderedRequest.body.fileName, 'base64'));
+ } catch (e) {
+ console.warn('[code gen] Failed to read file', e);
+ }
+ } else {
+ // For every other type, Insomnia uses the same body format as HAR
+ postData = renderedRequest.body;
+ }
return {
method: renderedRequest.method,
diff --git a/app/common/misc.js b/app/common/misc.js
index 6c4236fdce..5d6e6b5d1f 100644
--- a/app/common/misc.js
+++ b/app/common/misc.js
@@ -154,3 +154,25 @@ export function debounce (callback, millis = DEBOUNCE_MILLIS) {
callback.apply(null, results['__key__'])
}, millis).bind(null, '__key__');
}
+
+export function describeByteSize (bytes) {
+ bytes = Math.round(bytes * 10) / 10;
+ let size;
+
+ let unit = 'B';
+ if (bytes < 1024) {
+ size = bytes;
+ unit = 'B';
+ } else if (bytes < 1024 * 1024) {
+ size = bytes / 1024;
+ unit = 'KB';
+ } else if (bytes < 1024 * 1024) {
+ size = bytes / 1024 / 1024;
+ unit = 'MB';
+ } else {
+ size = bytes / 1024 / 1024 / 1024;
+ unit = 'GB';
+ }
+
+ return Math.round(size * 10) / 10 + ' ' + unit;
+}
diff --git a/app/common/network.js b/app/common/network.js
index d2ab69d3df..8a9405714c 100644
--- a/app/common/network.js
+++ b/app/common/network.js
@@ -9,6 +9,8 @@ import {jarFromCookies, cookiesFromJar, cookieHeaderValueForUri} from './cookies
import {setDefaultProtocol} from './misc';
import {getRenderedRequest} from './render';
import {swapHost} from './dns';
+import {CONTENT_TYPE_FILE} from './constants';
+import * as fs from 'fs';
let cancelRequestFunction = null;
@@ -50,6 +52,8 @@ export function _buildRequestConfig (renderedRequest, patch = {}) {
config.body = buildFromParams(renderedRequest.body.params || [], true);
} else if (renderedRequest.body.mimeType === CONTENT_TYPE_FORM_DATA) {
// TODO: This
+ } else if (renderedRequest.body.mimeType === CONTENT_TYPE_FILE) {
+ config.body = fs.readFileSync(renderedRequest.body.fileName);
} else {
config.body = renderedRequest.body.text || '';
}
@@ -85,14 +89,25 @@ export function _actuallySend (renderedRequest, settings, forceIPv4 = false) {
const proxyHost = protocol === 'https:' ? httpsProxy : httpProxy;
const proxy = proxyHost ? setDefaultProtocol(proxyHost) : null;
- const config = _buildRequestConfig(renderedRequest, {
- jar: null, // We're doing our own cookies
- proxy: proxy,
- followAllRedirects: settings.followRedirects,
- followRedirect: settings.followRedirects,
- timeout: settings.timeout > 0 ? settings.timeout : null,
- rejectUnauthorized: settings.validateSSL
- }, true);
+ let config;
+ try {
+ config = _buildRequestConfig(renderedRequest, {
+ jar: null, // We're doing our own cookies
+ proxy: proxy,
+ followAllRedirects: settings.followRedirects,
+ followRedirect: settings.followRedirects,
+ timeout: settings.timeout > 0 ? settings.timeout : null,
+ rejectUnauthorized: settings.validateSSL
+ }, true);
+ } catch (e) {
+ const response = await models.response.create({
+ parentId: renderedRequest._id,
+ elapsedTime: 0,
+ statusMessage: 'Error',
+ error: e.message
+ });
+ return resolve(response);
+ }
// Add the cookie header to the request
const cookieJar = renderedRequest.cookieJar;
diff --git a/app/models/request.js b/app/models/request.js
index a5039f821a..6bf485d783 100644
--- a/app/models/request.js
+++ b/app/models/request.js
@@ -3,6 +3,9 @@ import * as db from '../common/database';
import {getContentTypeHeader} from '../common/misc';
import {deconstructToParams} from '../common/querystring';
import {CONTENT_TYPE_JSON} from '../common/constants';
+import {CONTENT_TYPE_XML} from '../common/constants';
+import {CONTENT_TYPE_FILE} from '../common/constants';
+import {CONTENT_TYPE_TEXT} from '../common/constants';
export const name = 'Request';
export const type = 'Request';
@@ -22,6 +25,24 @@ export function init () {
};
}
+export function getBodyDescription (body) {
+ if (body.fileName) {
+ return 'File Upload';
+ } else if (body.mimeType === CONTENT_TYPE_FORM_URLENCODED) {
+ return 'Form Url Encoded';
+ } else if (body.mimeType === CONTENT_TYPE_FORM_DATA) {
+ return 'Form Data';
+ } else if (body.mimeType === CONTENT_TYPE_JSON) {
+ return 'JSON';
+ } else if (body.mimeType === CONTENT_TYPE_XML) {
+ return 'XML';
+ } else if (body.mimeType === CONTENT_TYPE_TEXT) {
+ return 'Plain Text';
+ } else {
+ return 'Raw Body';
+ }
+}
+
export function newBodyRaw (rawBody, contentType) {
if (!contentType) {
return {text: rawBody};
@@ -38,6 +59,13 @@ export function newBodyFormUrlEncoded (parameters) {
}
}
+export function newBodyFile (path) {
+ return {
+ mimeType: CONTENT_TYPE_FILE,
+ fileName: path
+ }
+}
+
export function newBodyForm (parameters) {
return {
mimeType: CONTENT_TYPE_FORM_DATA,
@@ -99,6 +127,8 @@ export function updateMimeType (request, mimeType) {
request.body = newBodyForm(request.body.params || []);
} else if (mimeType === CONTENT_TYPE_JSON) {
request.body = newBodyRaw(request.body.text || '');
+ } else if (mimeType === CONTENT_TYPE_FILE) {
+ request.body = newBodyFile('');
} else {
request.body = newBodyRaw(request.body.text || '', mimeType);
}
diff --git a/app/ui/components/RequestPane.js b/app/ui/components/RequestPane.js
index 598265b621..7784496d3a 100644
--- a/app/ui/components/RequestPane.js
+++ b/app/ui/components/RequestPane.js
@@ -9,6 +9,7 @@ import AuthEditor from './editors/AuthEditor';
import RequestUrlBar from './RequestUrlBar.js';
import {MOD_SYM, getContentTypeName, getContentTypeFromHeaders} from '../../common/constants';
import {debounce} from '../../common/misc';
+import {getBodyDescription} from '../../models/request';
class RequestPane extends Component {
render () {
diff --git a/app/ui/components/base/FileInputButton.js b/app/ui/components/base/FileInputButton.js
new file mode 100644
index 0000000000..13e27ee3ed
--- /dev/null
+++ b/app/ui/components/base/FileInputButton.js
@@ -0,0 +1,55 @@
+import React, {Component, PropTypes} from 'react';
+import {remote} from 'electron';
+import {Dropdown, DropdownButton, DropdownItem} from '../base/dropdown';
+import PromptButton from '../base/PromptButton';
+
+class FileInputButton extends Component {
+ _handleUnsetFile () {
+ this.props.onChange('');
+ }
+
+ _handleChooseFile () {
+ const options = {
+ title: 'Import File',
+ buttonLabel: 'Import',
+ properties: ['openFile']
+ };
+
+ remote.dialog.showOpenDialog(options, async paths => {
+ if (!paths || paths.length === 0) {
+ return;
+ }
+
+ const path = paths[0];
+ this.props.onChange(path);
+ })
+ }
+
+ render () {
+ const {className} = this.props;
+ return (
+
+ {path ? (
+
+
+ {pathDescription}
+
+ {" "}
+ ({sizeDescription})
+
+ ) : (
+ No file selected
+ )}
+