From 96152ec7c10dccd2bd66500f0e2f14b5e668ffe7 Mon Sep 17 00:00:00 2001 From: Steven Wittens Date: Mon, 13 Jun 2011 00:15:41 -0700 Subject: [PATCH] Use hex view for true binary files by default. --- Node/shell/meta.js | 36 +++++++++++++++++++++++------ Node/shell/reader.js | 55 +++++++++++++++++++++++++++++++++++++++----- Node/test.js | 4 ++-- todo.txt | 5 ++-- 4 files changed, 83 insertions(+), 17 deletions(-) diff --git a/Node/shell/meta.js b/Node/shell/meta.js index db38e82..7a35319 100644 --- a/Node/shell/meta.js +++ b/Node/shell/meta.js @@ -432,18 +432,40 @@ exports.headers.prototype = { * Mime type detection. */ exports.sniff = function (file, data) { - var parts = file.split('.'), - extension = parts.pop(); + var type = mime.lookup(file), + utf8 = true, + binary = false; + + // Detect valid UTF-8. + // Ignore trailing error due to possible truncation. + var attempt = data.toString('utf-8').replace(/�$/, ''), error = attempt.indexOf('�'); + if (error != -1) { + utf8 = false; + } - if ((type = mime.lookup(file)) && (type != 'application/octet-stream')){ - return type; + // Detect binary data. + if (/[\u0000]/(attempt)) { + binary = true; } - if (/[^\u0001-\uFFFF]/('' + data)) { - return 'application/octet-stream'; + // Plain text. + if (utf8 || !binary) { + return [ type, { charset: 'utf-8' } ]; } - return 'text/plain'; + // Specific binary type. + if (type != 'application/octet-stream') { + return type; + } + + // If data contains random binary data, then a significant part will be invalid / non-printable. + // Use hex-view. + if (attempt.replace(/[^\n\r\t\u0020-\uFFFC]/g, '').length < .8 * data.length) { + return [ type, { schema: 'termkit.hex' } ]; + } + + // Fallback, escaped binary text output. + return type; }; /** diff --git a/Node/shell/reader.js b/Node/shell/reader.js index 04979d4..728fb69 100644 --- a/Node/shell/reader.js +++ b/Node/shell/reader.js @@ -8,6 +8,16 @@ var fs = require('fs'), composePath = require('misc').composePath, expandPath = require('misc').expandPath; +// Is x an object? +function isObject(x) { + return typeof x == 'object'; +} + +// Is x an array? +function isArray(x) { + return isObject(x) && x.constructor == [].constructor; +} + /** * Data reader for termkit-style dataIn. * @@ -379,6 +389,32 @@ exports.filesReader.prototype = { return types[0]; } + // Merge multiple types. + var merged = {}, hasParams = false; + for (var i in types) { + // Process params. + if (isArray(types[i])) { + var params = types[i][1]; + + // Only merge identical params. + for (var j in params) { + if (merged[j] === null) continue; + if (merged[j] != params[j]) { + merged[j] = null; + } + merged[j] = params[j]; + } + + // Strip params for merge. + types[i] = types[i][0]; + } + } + // Check if there are params. + for (var j in merged) { + hasParams = true; + break; + } + function commonPrefix(types) { // Sort types, inspect first/last strings. types.sort(); @@ -399,6 +435,8 @@ exports.filesReader.prototype = { return match; } + var type = 'application/octed-stream'; + prefix = commonPrefix(types); if (!(/^[^\/]+\/[^\/]+$/(prefix))) { // If we only matched a type category (e.g. text/), @@ -410,22 +448,27 @@ exports.filesReader.prototype = { if (!(/^[^\/]+\/[^\/]+$/(prefix))) { // Replace with generic type. - return meta.default(prefix) || 'application/octet-stream'; + type = meta.default(prefix) || 'application/octet-stream'; + } + else { + type = prefix; } - - return prefix; } else { // Only return a unified type for content types which can be composed safely. var composable = meta.composable(); if (composable[prefix]) { if (typeof composable[prefix] == 'string') { - return composable[prefix]; + type = composable[prefix]; + } + else { + type = prefix; } - return prefix; } - return 'application/octet-stream'; } + + // Return with or without merged params. + return hasParams ? [ type, merged ] : type; }, /** diff --git a/Node/test.js b/Node/test.js index 7abf3d6..ecb8e88 100644 --- a/Node/test.js +++ b/Node/test.js @@ -43,7 +43,7 @@ function mockClient(flow, callback) { var messages = [], success = true, i; client.send = function (message) { - messages.push(JSON.parse(message)); + messages.push(message); }; client.disconnect = function (message) { @@ -53,7 +53,7 @@ function mockClient(flow, callback) { var r = new router.router(client); for (i in flow) (function (message) { setTimeout(function () { - client.emit('message', JSON.stringify(message)); + client.emit('message', message); }, i * 100); })(flow[i]); diff --git a/todo.txt b/todo.txt index 9196b97..5605283 100644 --- a/todo.txt +++ b/todo.txt @@ -3,8 +3,9 @@ Tasks: [X] redefine ansi colors [X] make fileReader with same pattern as reader.js [X] cat multiple files -> octet-stream w/ type coercion -[ ] hex widget -[ ] hex utility +[X] hex widget +[X] hex utility +[ ] hex detection [ ] backgrounding / new command trigger [ ] move escapeUnixText into front-end, unixLegacy widget [ ] command aliases