Browse Source

Downloading of files via wetty terminal (#206)

* Feature for download of files via Blob

* Document file download feature and bump version.
pull/162/head
Ben Letchford 5 years ago
committed by Cian Butler
parent
commit
11f715e1b0
  1. 29
      README.md
  2. 7
      package.json
  3. 78
      src/client/index.ts
  4. 6
      src/client/wetty.scss
  5. 10
      yarn.lock

29
README.md

@ -134,6 +134,35 @@ the user like this (Only while running wetty as a non root account):
This is not a required feature and the security implications for passing the
password in the url will have to be considered by the user
### File Downloading
Wetty supports file downloads by printing terminal escape sequences between a
base64 encoded file.
The terminal escape sequences used are `^[[5i` and `^[[4i` (VT100 for "enter
auto print" and "exit auto print" respectively -
https://vt100.net/docs/tp83/appendixc.html).
An example of a helper script that prints the terminal escape characters and
base64s stdin:
```
$ cat wetty-download.sh
#!/bin/sh
echo '^[[5i'$(cat /dev/stdin | base64)'^[[4i'
```
You are then able to download files via wetty!
```
$ cat my-pdf-file.pdf | ./wetty-download.sh
```
Wetty will then issue a popup like the following that links to a local file
blob:
`Download ready: file-20191015233654.pdf`
## Run wetty behind nginx or apache
As said earlier you can use a proxy to add https to WeTTy.

7
package.json

@ -1,6 +1,6 @@
{
"name": "wetty",
"version": "1.1.7",
"version": "1.1.8",
"description": "WeTTY = Web + TTY. Terminal access in browser over http/https",
"homepage": "https://github.com/krishnasrinivas/wetty",
"repository": {
@ -51,6 +51,7 @@
"dependencies": {
"compression": "^1.7.4",
"express": "^4.17.1",
"file-type": "^12.3.0",
"fs-extra": "^8.1.0",
"helmet": "^3.20.1",
"lodash": "^4.17.15",
@ -60,6 +61,7 @@
"socket.io": "^2.2.0",
"socket.io-client": "^2.2.0",
"source-map-loader": "^0.2.4",
"toastify-js": "^1.6.1",
"winston": "^3.2.1",
"xterm": "^3.14.5",
"yargs": "^14.0.0"
@ -126,6 +128,7 @@
"Strubbl <github@linux4tw.de>",
"koushikmln <mln02koushik@gmail.com>",
"mirtouf <mirtouf@gmail.com>",
"nosemeocurrenada <nosemeocurrenada93@gmail.com>"
"nosemeocurrenada <nosemeocurrenada93@gmail.com>",
"Ben Letchford <contact@benl.com.au>"
]
}

78
src/client/index.ts

@ -2,6 +2,8 @@ import { Terminal } from 'xterm';
import { isUndefined } from 'lodash';
import * as io from 'socket.io-client';
import { fit } from 'xterm/lib/addons/fit/fit';
import * as fileType from 'file-type';
import Toastify from 'toastify-js';
import './wetty.scss';
import './favicon.ico';
@ -12,8 +14,12 @@ const socket = io(window.location.origin, {
path: `${trim(socketBase)}/socket.io`,
});
const FILE_BEGIN = '\u001b[5i';
const FILE_END = '\u001b[4i';
socket.on('connect', () => {
const term = new Terminal();
let fileBuffer = [];
term.open(document.getElementById('terminal'));
const defaultOptions = { fontSize: 14 };
let options: object;
@ -82,6 +88,52 @@ socket.on('connect', () => {
disconnect(data);
}
function onCompleteFile() {
let bufferCharacters = fileBuffer.join('');
bufferCharacters = bufferCharacters.substring(bufferCharacters.lastIndexOf(FILE_BEGIN) + FILE_BEGIN.length, bufferCharacters.lastIndexOf(FILE_END));
// Try to decode it as base64, if it fails we assume it's not base64
try {
bufferCharacters = window.atob(bufferCharacters);
} catch (err) {
// Assuming it's not base64...
}
const bytes = new Uint8Array(bufferCharacters.length);
for (let i = 0; i < bufferCharacters.length; i += 1) {
bytes[i] = bufferCharacters.charCodeAt(i);
}
let mimeType = 'application/octet-stream';
let fileExt = '';
const typeData = fileType(bytes);
if (typeData) {
mimeType = typeData.mime;
fileExt = typeData.ext;
}
const fileName = `file-${new Date()
.toISOString()
.split('.')[0]
.replace(/-/g, '')
.replace('T', '')
.replace(/:/g, '')}${fileExt ? `.${fileExt}` : ''}`;
const blob = new Blob([new Uint8Array(bytes.buffer)], { type: mimeType });
const blobUrl = URL.createObjectURL(blob);
fileBuffer = [];
Toastify({
text: `Download ready: <a href="${blobUrl}" target="_blank" download="${fileName}">${fileName}</a>`,
duration: 10000,
newWindow: true,
gravity: 'bottom',
position: 'right',
backgroundColor: '#fff',
stopOnFocus: true,
}).showToast();
}
term.on('data', data => {
socket.emit('input', data);
});
@ -90,7 +142,31 @@ socket.on('connect', () => {
});
socket
.on('data', (data: string) => {
term.write(data);
const indexOfFileBegin = data.indexOf(FILE_BEGIN);
const indexOfFileEnd = data.indexOf(FILE_END);
// If we've got the entire file in one chunk
if (indexOfFileBegin !== -1 && indexOfFileEnd !== -1) {
fileBuffer.push(data);
onCompleteFile();
}
// If we've found a beginning marker
else if (indexOfFileBegin !== -1) {
fileBuffer.push(data);
}
// If we've found an ending marker
else if (indexOfFileEnd !== -1) {
fileBuffer.push(data);
onCompleteFile();
}
// If we've found the continuation of a file
else if (fileBuffer.length > 0) {
fileBuffer.push(data);
}
// Just treat it as normal data
else {
term.write(data);
}
})
.on('login', () => {
term.writeln('');

6
src/client/wetty.scss

@ -1,4 +1,5 @@
@import '~xterm/dist/xterm';
@import '~toastify-js/src/toastify.css';
$black: #000;
$grey: rgba(0, 0, 0, 0.75);
@ -94,4 +95,9 @@ body {
display: flex;
}
}
.toastify {
border-radius: 0;
color: $black;
}
}

10
yarn.lock

@ -3152,6 +3152,11 @@ file-loader@^4.2.0:
loader-utils "^1.2.3"
schema-utils "^2.0.0"
file-type@^12.3.0:
version "12.3.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-12.3.0.tgz#74d755e5dc9c5cbc7ee6f182529b453906ac88c2"
integrity sha512-4E4Esq9KLwjYCY32E7qSmd0h7LefcniZHX+XcdJ4Wfx1uGJX7QCigiqw/U0yT7WOslm28yhxl87DJ0wHYv0RAA==
fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
@ -7196,6 +7201,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
toastify-js@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/toastify-js/-/toastify-js-1.6.1.tgz#2ec20654925d6f83f935d5a6907c146e6bcb67d6"
integrity sha512-yosiXPEdr3B9KL1rF7M/IMdw8d8Z69UJe2JsvxbfdpaL2/olqSB8tvp7/N6gkT9G46y6nqR2e62CCnf0LxeIBQ==
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"

Loading…
Cancel
Save