From 690bca781fa5f5444603fd24dc34e7042738d03c Mon Sep 17 00:00:00 2001 From: Jameson Little Date: Sat, 4 Jun 2011 13:58:10 -0600 Subject: [PATCH] Example works --- LICENSE | 21 ++++++++ README.md | 45 ++++++++++++++++ examples/index.html | 36 +++++++++++++ lib/header.js | 129 ++++++++++++++++++++++++++++++++++++++++++++ lib/tar.js | 113 ++++++++++++++++++++++++++++++++++++++ lib/utils.js | 95 ++++++++++++++++++++++++++++++++ package.json | 19 +++++++ 7 files changed, 458 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 examples/index.html create mode 100644 lib/header.js create mode 100644 lib/tar.js create mode 100644 lib/utils.js create mode 100644 package.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be5f115 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2011 T. Jameson Little + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ecfa499 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +Intro +===== + +Have you ever wanted to tar something, but you didn't want to push it to your server first? + +Tar-js is here to the rescue!! + +With tar-js, you can construct a tar archive in the browser. This is basically a port of tar-async for Nodejs for the browser, with a couple differences. + +Here's what it supports: + * Add strings to a tar archive as files + * Customizable uid, gid, mtime, and permissions (defaults work well though too) + * Add files in a directory heirarchy + +Dependencies +------------ + +Tar needs an HTML5 compliant browser. More specifically it needs `Uint8Array` to work. + +The only external module is require-kiss, which makes browser JS much more Node-like. + +This module can be installed from npm (`npm install require-kiss`) or directly downloaded from github (https://github.com/coolaj86/require-kiss-js). + +Usage Guide +=========== + +In your HTML file, make sure that require-kiss is included first. Then, to use it, do something like this: + + var Tar = require('tar-js'), + tape = new Tar(); + +Then all you got to do is call `tape.append` with your params and it'll be added to the archive. That's it! + +Here's the api for append: `append(filepath, content, [opts], [callback])` + +* filepath- string path (can include directories and such) +* content- string or Uint8Array +* opts- options: + * mode- permissions of resulting file (octet) [default: 777] + * mtime- modification time in seconds (integer) [default: current time] + * uid- user id (integer) [default: 0] + * gid- group id (integer) [default: 0] +* callback- callback when done (takes a Uint8Array as it's only parameter) + * This is a reference to the tar so far + * Copy it if you want to use it, because subsequent adds may break stuff diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 0000000..2c024f5 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,36 @@ + + + +Browser Tar + + + + + + + + + + + + diff --git a/lib/header.js b/lib/header.js new file mode 100644 index 0000000..c401690 --- /dev/null +++ b/lib/header.js @@ -0,0 +1,129 @@ +/* + * tar-js + * MIT (c) 2011 T. Jameson Little + */ + +(function () { + "use strict"; + + require('require-kiss'); + +/* +struct posix_header { // byte offset + char name[100]; // 0 + char mode[8]; // 100 + char uid[8]; // 108 + char gid[8]; // 116 + char size[12]; // 124 + char mtime[12]; // 136 + char chksum[8]; // 148 + char typeflag; // 156 + char linkname[100]; // 157 + char magic[6]; // 257 + char version[2]; // 263 + char uname[32]; // 265 + char gname[32]; // 297 + char devmajor[8]; // 329 + char devminor[8]; // 337 + char prefix[155]; // 345 + // 500 +}; +*/ + + var utils = require("./utils"), + headerFormat; + + headerFormat = [ + { + 'field': 'fileName', + 'length': 100 + }, + { + 'field': 'fileMode', + 'length': 8 + }, + { + 'field': 'uid', + 'length': 8 + }, + { + 'field': 'gid', + 'length': 8 + }, + { + 'field': 'fileSize', + 'length': 12 + }, + { + 'field': 'mtime', + 'length': 12 + }, + { + 'field': 'checksum', + 'length': 8 + }, + { + 'field': 'type', + 'length': 1 + }, + { + 'field': 'linkName', + 'length': 100 + }, + { + 'field': 'ustar', + 'length': 8 + }, + { + 'field': 'owner', + 'length': 32 + }, + { + 'field': 'group', + 'length': 32 + }, + { + 'field': 'majorNumber', + 'length': 8 + }, + { + 'field': 'minorNumber', + 'length': 8 + }, + { + 'field': 'filenamePrefix', + 'length': 155 + }, + { + 'field': 'padding', + 'length': 12 + } + ]; + + function formatHeader(data, cb) { + var buffer = utils.clean(512), + offset = 0; + + headerFormat.forEach(function (value) { + var str = data[value.field] || "", + i, length; + + for (i = 0, length = str.length; i < length; i += 1) { + buffer[offset] = str.charCodeAt(i); + offset += 1; + } + + offset += value.length - i; // space it out with nulls + }); + + if (typeof cb === 'function') { + return cb(buffer, offset); + } + return buffer; + } + + module.exports.structure = headerFormat; + module.exports.format = formatHeader; + + provide('header', module.exports); +}()); diff --git a/lib/tar.js b/lib/tar.js new file mode 100644 index 0000000..161706e --- /dev/null +++ b/lib/tar.js @@ -0,0 +1,113 @@ +/* + * tar-js + * MIT (c) 2011 T. Jameson Little + */ + +(function () { + "use strict"; + + require('require-kiss'); + + var header = require("./header"), + utils = require("./utils"), + recordSize = 512, + blockSize; + + function Tar(recordsPerBlock) { + this.written = 0; + blockSize = (recordsPerBlock || 20) * recordSize; + this.out = utils.clean(blockSize); + } + + Tar.prototype.append = function (filepath, input, opts, callback) { + var data, + checksum, + mode, + mtime, + uid, + gid, + headerArr; + + if (typeof input === 'string') { + input = utils.stringToUint8(input); + } else if (input.constructor !== Uint8Array.prototype.constructor) { + throw 'Invalid input type. You gave me: ' + input.constructor.toString().match(/function\s*([$A-Za-z_][0-9A-Za-z_]*)\s*\(/)[1]; + } + + if (typeof opts === 'function') { + callback = opts; + opts = {}; + } + + opts = opts || {}; + + mode = opts.mode || parseInt('777', 8) & 0xfff; + mtime = opts.mtime || Math.floor(+new Date() / 1000); + uid = opts.uid || 0; + gid = opts.gid || 0; + + data = { + fileName: filepath, + fileMode: utils.pad(mode, 7), + uid: utils.pad(uid, 7), + gid: utils.pad(gid, 7), + fileSize: utils.pad(input.length, 11), + mtime: utils.pad(mtime, 11), + checksum: ' ', + type: '0', // just a file + ustar: 'ustar ', + owner: '', + group: '' + }; + + // calculate the checksum + checksum = 0; + Object.keys(data).forEach(function (key) { + var i, value = data[key], length; + + for (i = 0, length = value.length; i < length; i += 1) { + checksum += value.charCodeAt(i); + } + }); + + data.checksum = utils.pad(checksum, 6) + "\u0000 "; + + headerArr = header.format(data); + + var i, offset, length; + + this.out.set(headerArr, this.written); + + this.written += headerArr.length; + + // this makes sense if the input is greater than 512 bytes + if (headerArr.length + input.length > this.out.length) { + this.out = utils.extend(out, headerArr.length, input.length, blockSize); + } + + this.out.set(input, this.written); + + // to the nearest multiple of recordSize + this.written += input.length + (recordSize - (input.length % recordSize)); + + // make sure there's at least 2 empty records worth of extra space + if (this.out.length - this.written < recordSize * 2) { + this.out = utils.extend(out, this.written, recordSize * 2, blockSize); + } + + if (typeof callback === 'function') { + callback(this.out); + } + + return this.out; + }; + + Tar.prototype.clear = function () { + this.written = 0; + this.out = utils.clean(blockSize); + }; + + module.exports = Tar; + + provide('tar', module.exports); +}()); diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..292a3aa --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,95 @@ +/* + * tar-js + * MIT (c) 2011 T. Jameson Little + */ + +(function () { + "use strict"; + + require('require-kiss'); + + var lookup = [ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + ]; + function clean(length) { + var i, buffer = new Uint8Array(length); + for (i = 0; i < length; i += 1) { + buffer[i] = 0; + } + return buffer; + } + + function extend(orig, length, addLength, multipleOf) { + var newSize = length + addLength, + buffer = clean((parseInt(newSize / multipleOf) + 1) * multipleOf); + + buffer.set(orig); + + return buffer; + } + + function pad(num, bytes, base) { + num = num.toString(base || 8); + return "000000000000".substr(num.length + 12 - bytes) + num; + } + + function stringToUint8 (input, out, offset) { + var i, length; + + out = out || clean(input.length); + + offset = offset || 0; + for (i = 0, length = input.length; i < length; i += 1) { + out[offset] = input.charCodeAt(i); + offset += 1; + } + + return out; + } + + function uint8ToBase64(uint8) { + var i, + extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes + output = "", + temp, length; + + function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]; + }; + + // go through the array every three bytes, we'll deal with trailing stuff later + for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { + temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]); + output += tripletToBase64(temp); + } + + // this prevents an ERR_INVALID_URL in Chrome (Firefox okay) + switch (output.length % 4) { + case 1: + output += '='; + break; + case 2: + output += '=='; + break; + default: + break; + } + + return output; + } + + module.exports.clean = clean; + module.exports.pad = pad; + module.exports.extend = extend; + module.exports.stringToUint8 = stringToUint8; + module.exports.uint8ToBase64 = uint8ToBase64; + + provide('utils'); +}()); diff --git a/package.json b/package.json new file mode 100644 index 0000000..36d5f1b --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "tar-js", + "description": "Tar implemented in the browser", + "version": "0.1.0", + "homepage": "http://github.com/beatgammit/tar-js", + "repository": { + "type": "git", + "url": "git://github.com/beatgammit/tar-js.git" + }, + "author": "T. Jameson Little ", + "main": "lib/tar.js", + "keywords": ["tar", "browser", "client", "offline"], + "directories": { + "lib": "lib" + }, + "dependencies": { + "require-kiss": "*" + } +}