Added --linkify option. Default settings cause the terminal to recognize fully qualified URLs and to make them clickable.

git-svn-id: https://shellinabox.googlecode.com/svn/trunk@134 0da03de8-d603-11dd-86c2-0f8696b7b6f9
This commit is contained in:
zodiac 2009-07-04 08:16:07 +00:00
parent ee1cd17b18
commit 19177de98c
12 changed files with 468 additions and 8 deletions

View file

@ -1,3 +1,8 @@
2009-07-03 Markus Gutschke <markus@shellinabox.com>
* Added --linkify option. Default settings cause the terminal to
recognize fully qualified URLs and to make them clickable.
2009-06-28 Markus Gutschke <markus@shellinabox.com>
* Added support for keyboards that have a dedicated "<" / ">" key.

View file

@ -132,7 +132,7 @@
#define STDC_HEADERS 1
/* Most recent revision number in the version control system */
#define VCS_REVISION "133"
#define VCS_REVISION "134"
/* Version number of package */
#define VERSION "2.8"

2
configure vendored
View file

@ -2037,7 +2037,7 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
ac_compiler_gnu=$ac_cv_c_compiler_gnu
VCS_REVISION=133
VCS_REVISION=134
cat >>confdefs.h <<_ACEOF

View file

@ -2,7 +2,7 @@ AC_PREREQ(2.57)
dnl This is the one location where the authoritative version number is stored
AC_INIT(shellinabox, 2.8, markus@shellinabox.com)
VCS_REVISION=133
VCS_REVISION=134
AC_SUBST(VCS_REVISION)
AC_DEFINE_UNQUOTED(VCS_REVISION, "${VCS_REVISION}",
[Most recent revision number in the version control system])

View file

@ -1,3 +1,12 @@
#vt100 a {
text-decoration: none;
color: inherit;
}
#vt100 a:hover {
text-decoration: underline;
}
#vt100 #reconnect {
position: absolute;
z-index: 2;

View file

@ -96,6 +96,82 @@
// #define MOUSE_CLICK 2
function VT100(container) {
if (typeof linkifyURLs == 'undefined' && linkifyURLs > 0) {
this.urlRE = null;
} else {
this.urlRE = new RegExp(
// Known URL protocol are "http", "https", and "ftp".
'(?:http|https|ftp)://' +
// Optionally allow username and passwords.
'(?:[^:@/ ]*(?::[^@/ ]*)?@)?' +
// Hostname.
'(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
'[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
'(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u009F]+)' +
// Port
'(?::[1-9][0-9]*)?' +
// Path.
'(?:/[^/,.) ]*)*|' +
(linkifyURLs <= 1 ? '' :
// Also support URLs without a protocol (assume "http").
// Optional username and password.
'(?:[^:@/ ]*(?::[^@/ ]*)?@)?' +
// Hostnames must end with a well-known top-level domain or must be
// numeric.
'(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
'localhost|' +
'(?:(?!-)[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u009F]+[.]){2,}' +
'(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|' +
'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
'yu|za|zm|zw|arpa|[Xx][Nn]--[-a-zA-Z0-9]+))' +
// Port
'(?::[1-9][0-9]{0,4})?' +
// Path.
'(?:/[^/,.) ]*)*|') +
// In addition, support e-mail address. Optionally, recognize "mailto:"
'(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
// Username:
'[-_.+a-zA-Z0-9]+@' +
// Hostname.
'(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
'(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|' +
'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
'yu|za|zm|zw|arpa|[Xx][Nn]--[-a-zA-Z0-9]+)' +
// Optional arguments
'(?:[?][^/,.) ]+)?');
}
this.initializeElements(container);
this.initializeAnsiColors();
this.maxScrollbackLines = 500;
@ -695,12 +771,72 @@ VT100.prototype.mouseEvent = function(event, type) {
return true;
};
VT100.prototype.replaceChar = function(s, ch, repl) {
for (var i = -1;;) {
i = s.indexOf(ch, i + 1);
if (i < 0) {
break;
}
s = s.substr(0, i) + repl + s.substr(i + 1);
}
return s;
};
VT100.prototype.htmlEscape = function(s) {
return this.replaceChar(this.replaceChar(this.replaceChar(
s, '&', '&amp;'), '<', '&lt;'), '"', '&quot;');
};
VT100.prototype.getTextContent = function(elem) {
return elem.textContent ||
(typeof elem.textContent == 'undefined' ? elem.innerText : '');
};
VT100.prototype.setTextContent = function(elem, s) {
// Check if we find any URLs in the text. If so, automatically convert them
// to links.
if (this.urlRE && this.urlRE.test(s)) {
var inner = '';
for (;;) {
var consumed = 0;
if (RegExp.leftContext != null) {
inner += this.htmlEscape(RegExp.leftContext);
consumed += RegExp.leftContext.length;
}
var url = this.htmlEscape(RegExp.lastMatch);
var fullUrl = url;
// If no protocol was specified, try to guess a reasonable one.
if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
var slash = url.indexOf('/');
var at = url.indexOf('@');
var question = url.indexOf('?');
if (at > 0 &&
(at < question || question < 0) &&
(slash < 0 || (question > 0 && slash > question))) {
fullUrl = 'mailto:' + url;
} else {
fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
url;
}
}
inner += '<a target="vt100Link" href="' + fullUrl +
'">' + url + '</a>';
consumed += RegExp.lastMatch.length;
s = s.substr(consumed);
if (!this.urlRE.test(s)) {
if (RegExp.rightContext != null) {
inner += this.htmlEscape(RegExp.rightContext);
}
break;
}
}
elem.innerHTML = inner;
return;
}
// Updating the content of an element is an expensive operation. It actually
// pays off to first check whether the element is still unchanged.
if (typeof elem.textContent == 'undefined') {
@ -1500,7 +1636,7 @@ VT100.prototype.toggleBell = function() {
};
VT100.prototype.about = function() {
alert("VT100 Terminal Emulator " + "2.8 (revision 133)" +
alert("VT100 Terminal Emulator " + "2.8 (revision 134)" +
"\nCopyright 2008-2009 by Markus Gutschke\n" +
"For more information check http://shellinabox.com");
};

View file

@ -355,7 +355,7 @@ ShellInABox.prototype.extendContextMenu = function(entries, actions) {
};
ShellInABox.prototype.about = function() {
alert("Shell In A Box version " + "2.8 (revision 133)" +
alert("Shell In A Box version " + "2.8 (revision 134)" +
"\nCopyright 2008-2009 by Markus Gutschke\n" +
"For more information check http://shellinabox.com" +
(typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?

View file

@ -84,6 +84,7 @@ static int noBeep = 0;
static int numericHosts = 0;
static int enableSSL = 1;
static int enableSSLMenu = 1;
static int linkifyURLs = 1;
static char *certificateDir;
static int certificateFd = -1;
static HashMap *externalFiles;
@ -522,10 +523,12 @@ static int shellInABoxHttpHandler(HttpConnection *http, void *arg,
char *stateVars = stringPrintf(NULL,
"serverSupportsSSL = %s;\n"
"disableSSLMenu = %s;\n"
"suppressAllAudio = %s;\n\n",
"suppressAllAudio = %s;\n"
"linkifyURLs = %d;\n\n",
enableSSL ? "true" : "false",
!enableSSLMenu ? "true" : "false",
noBeep ? "true" : "false");
noBeep ? "true" : "false",
linkifyURLs);
int stateVarsLength = strlen(stateVars);
int contentLength = stateVarsLength +
(addr(vt100End) - addr(vt100Start)) +
@ -596,6 +599,7 @@ static void usage(void) {
" -f, --static-file=URL:FILE serve static file from URL path\n"
" -g, --group=GID switch to this group (default: %s)\n"
" -h, --help print this message\n"
" --linkify=[none|normal|agressive] default is \"normal\"\n"
" --localhost-only only listen on 127.0.0.1\n"
" --no-beep suppress all audio output\n"
" -n, --numeric do not resolve hostnames\n"
@ -666,6 +670,7 @@ static void parseArgs(int argc, char * const argv[]) {
{ "debug", 0, 0, 'd' },
{ "static-file", 1, 0, 'f' },
{ "group", 1, 0, 'g' },
{ "linkify", 1, 0, 0 },
{ "localhost-only", 0, 0, 0 },
{ "no-beep", 0, 0, 0 },
{ "numeric", 0, 0, 'n' },
@ -784,6 +789,18 @@ static void parseArgs(int argc, char * const argv[]) {
fatal("Duplicate --group option.");
}
runAsGroup = parseGroup(optarg, NULL);
} else if (!idx--) {
// Linkify
if (!strcmp(optarg, "none")) {
linkifyURLs = 0;
} else if (!strcmp(optarg, "normal")) {
linkifyURLs = 1;
} else if (!strcmp(optarg, "aggressive")) {
linkifyURLs = 2;
} else {
fatal("Invalid argument for --linkify. Must be "
"\"none\", \"normal\", or \"aggressive\".");
}
} else if (!idx--) {
// Localhost Only
localhostOnly = 1;

View file

@ -58,6 +58,7 @@ shellinaboxd \- publish command line shell through AJAX interface
[\ \fB-f\fP\ | \fB--static-file=\fP\fIurl\fP:\fIfile\fP\ ]
[\ \fB-g\fP\ | \fB--group=\fP\fIgid\fP\ ]
[\ \fB-h\fP\ | \fB--help\fP\ ]
[\ \fB--linkify\fP=[\fBnone\fP|\fBnormal\fP|\fBaggressive\fP]\ ]
[\ \fB--localhost-only\fP\ ]
[\ \fB--no-beep\fP\ ]
[\ \fB-n\fP\ | \fB--numeric\fP\ ]
@ -252,6 +253,17 @@ runs as.
\fB-h\fP\ |\ \fB--help\fP
Display a brief usage message showing the valid command line parameters.
.TP
\fB--linkify\fP=[\fBnone\fP|\fBnormal\fP|\fBaggressive\fP]
the daemon attempts to recognize URLs in the terminal output and makes them
clickable. This is not neccessarily a fool-proof process and both false
negatives and false positives are possible. By default, only URLs starting
with a well known protocol of
.BR http:// ,\ https:// ,\ ftp:// ,\ or\ mailto:
are recognized. In
.B aggressive
mode, anything that looks like a hostname, URL or e-mail address is
recognized, even if not preceded by a protocol.
.TP
\fB--localhost-only\fP
Normally,
.B shellinaboxd

View file

@ -1,3 +1,12 @@
#vt100 a {
text-decoration: none;
color: inherit;
}
#vt100 a:hover {
text-decoration: underline;
}
#vt100 #reconnect {
position: absolute;
z-index: 2;

View file

@ -96,6 +96,82 @@
// #define MOUSE_CLICK 2
function VT100(container) {
if (typeof linkifyURLs == 'undefined' && linkifyURLs > 0) {
this.urlRE = null;
} else {
this.urlRE = new RegExp(
// Known URL protocol are "http", "https", and "ftp".
'(?:http|https|ftp)://' +
// Optionally allow username and passwords.
'(?:[^:@/ ]*(?::[^@/ ]*)?@)?' +
// Hostname.
'(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
'[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
'(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u009F]+)' +
// Port
'(?::[1-9][0-9]*)?' +
// Path.
'(?:/[^/,.) ]*)*|' +
(linkifyURLs <= 1 ? '' :
// Also support URLs without a protocol (assume "http").
// Optional username and password.
'(?:[^:@/ ]*(?::[^@/ ]*)?@)?' +
// Hostnames must end with a well-known top-level domain or must be
// numeric.
'(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
'localhost|' +
'(?:(?!-)[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u009F]+[.]){2,}' +
'(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|' +
'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
'yu|za|zm|zw|arpa|[Xx][Nn]--[-a-zA-Z0-9]+))' +
// Port
'(?::[1-9][0-9]{0,4})?' +
// Path.
'(?:/[^/,.) ]*)*|') +
// In addition, support e-mail address. Optionally, recognize "mailto:"
'(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
// Username:
'[-_.+a-zA-Z0-9]+@' +
// Hostname.
'(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
'(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|' +
'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
'yu|za|zm|zw|arpa|[Xx][Nn]--[-a-zA-Z0-9]+)' +
// Optional arguments
'(?:[?][^/,.) ]+)?');
}
this.initializeElements(container);
this.initializeAnsiColors();
this.maxScrollbackLines = 500;
@ -695,12 +771,72 @@ VT100.prototype.mouseEvent = function(event, type) {
return true;
};
VT100.prototype.replaceChar = function(s, ch, repl) {
for (var i = -1;;) {
i = s.indexOf(ch, i + 1);
if (i < 0) {
break;
}
s = s.substr(0, i) + repl + s.substr(i + 1);
}
return s;
};
VT100.prototype.htmlEscape = function(s) {
return this.replaceChar(this.replaceChar(this.replaceChar(
s, '&', '&amp;'), '<', '&lt;'), '"', '&quot;');
};
VT100.prototype.getTextContent = function(elem) {
return elem.textContent ||
(typeof elem.textContent == 'undefined' ? elem.innerText : '');
};
VT100.prototype.setTextContent = function(elem, s) {
// Check if we find any URLs in the text. If so, automatically convert them
// to links.
if (this.urlRE && this.urlRE.test(s)) {
var inner = '';
for (;;) {
var consumed = 0;
if (RegExp.leftContext != null) {
inner += this.htmlEscape(RegExp.leftContext);
consumed += RegExp.leftContext.length;
}
var url = this.htmlEscape(RegExp.lastMatch);
var fullUrl = url;
// If no protocol was specified, try to guess a reasonable one.
if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
var slash = url.indexOf('/');
var at = url.indexOf('@');
var question = url.indexOf('?');
if (at > 0 &&
(at < question || question < 0) &&
(slash < 0 || (question > 0 && slash > question))) {
fullUrl = 'mailto:' + url;
} else {
fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
url;
}
}
inner += '<a target="vt100Link" href="' + fullUrl +
'">' + url + '</a>';
consumed += RegExp.lastMatch.length;
s = s.substr(consumed);
if (!this.urlRE.test(s)) {
if (RegExp.rightContext != null) {
inner += this.htmlEscape(RegExp.rightContext);
}
break;
}
}
elem.innerHTML = inner;
return;
}
// Updating the content of an element is an expensive operation. It actually
// pays off to first check whether the element is still unchanged.
if (typeof elem.textContent == 'undefined') {
@ -1500,7 +1636,7 @@ VT100.prototype.toggleBell = function() {
};
VT100.prototype.about = function() {
alert("VT100 Terminal Emulator " + "2.8 (revision 133)" +
alert("VT100 Terminal Emulator " + "2.8 (revision 134)" +
"\nCopyright 2008-2009 by Markus Gutschke\n" +
"For more information check http://shellinabox.com");
};

View file

@ -96,6 +96,82 @@
#define MOUSE_CLICK 2
function VT100(container) {
if (typeof linkifyURLs == 'undefined' && linkifyURLs > 0) {
this.urlRE = null;
} else {
this.urlRE = new RegExp(
// Known URL protocol are "http", "https", and "ftp".
'(?:http|https|ftp)://' +
// Optionally allow username and passwords.
'(?:[^:@/ ]*(?::[^@/ ]*)?@)?' +
// Hostname.
'(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
'[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
'(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u009F]+)' +
// Port
'(?::[1-9][0-9]*)?' +
// Path.
'(?:/[^/,.) ]*)*|' +
(linkifyURLs <= 1 ? '' :
// Also support URLs without a protocol (assume "http").
// Optional username and password.
'(?:[^:@/ ]*(?::[^@/ ]*)?@)?' +
// Hostnames must end with a well-known top-level domain or must be
// numeric.
'(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
'localhost|' +
'(?:(?!-)[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u009F]+[.]){2,}' +
'(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|' +
'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
'yu|za|zm|zw|arpa|[Xx][Nn]--[-a-zA-Z0-9]+))' +
// Port
'(?::[1-9][0-9]{0,4})?' +
// Path.
'(?:/[^/,.) ]*)*|') +
// In addition, support e-mail address. Optionally, recognize "mailto:"
'(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
// Username:
'[-_.+a-zA-Z0-9]+@' +
// Hostname.
'(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
'(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|' +
'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
'yu|za|zm|zw|arpa|[Xx][Nn]--[-a-zA-Z0-9]+)' +
// Optional arguments
'(?:[?][^/,.) ]+)?');
}
this.initializeElements(container);
this.initializeAnsiColors();
this.maxScrollbackLines = 500;
@ -695,12 +771,72 @@ VT100.prototype.mouseEvent = function(event, type) {
return true;
};
VT100.prototype.replaceChar = function(s, ch, repl) {
for (var i = -1;;) {
i = s.indexOf(ch, i + 1);
if (i < 0) {
break;
}
s = s.substr(0, i) + repl + s.substr(i + 1);
}
return s;
};
VT100.prototype.htmlEscape = function(s) {
return this.replaceChar(this.replaceChar(this.replaceChar(
s, '&', '&amp;'), '<', '&lt;'), '"', '&quot;');
};
VT100.prototype.getTextContent = function(elem) {
return elem.textContent ||
(typeof elem.textContent == 'undefined' ? elem.innerText : '');
};
VT100.prototype.setTextContent = function(elem, s) {
// Check if we find any URLs in the text. If so, automatically convert them
// to links.
if (this.urlRE && this.urlRE.test(s)) {
var inner = '';
for (;;) {
var consumed = 0;
if (RegExp.leftContext != null) {
inner += this.htmlEscape(RegExp.leftContext);
consumed += RegExp.leftContext.length;
}
var url = this.htmlEscape(RegExp.lastMatch);
var fullUrl = url;
// If no protocol was specified, try to guess a reasonable one.
if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
var slash = url.indexOf('/');
var at = url.indexOf('@');
var question = url.indexOf('?');
if (at > 0 &&
(at < question || question < 0) &&
(slash < 0 || (question > 0 && slash > question))) {
fullUrl = 'mailto:' + url;
} else {
fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
url;
}
}
inner += '<a target="vt100Link" href="' + fullUrl +
'">' + url + '</a>';
consumed += RegExp.lastMatch.length;
s = s.substr(consumed);
if (!this.urlRE.test(s)) {
if (RegExp.rightContext != null) {
inner += this.htmlEscape(RegExp.rightContext);
}
break;
}
}
elem.innerHTML = inner;
return;
}
// Updating the content of an element is an expensive operation. It actually
// pays off to first check whether the element is still unchanged.
if (typeof elem.textContent == 'undefined') {