New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

ost2go

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ost2go - npm Package Compare versions

Comparing version
2.1.1
to
2.1.2
+28
-0
CHANGELOG.md

@@ -8,2 +8,30 @@ # Changelog

## [2.1.2] - 2025-10-17
### 🎨 Enhanced User Interface
#### Added
- **Colorful Help Display**: Completely redesigned help output with vibrant colors
- Cyan bordered header with version display (🚀 OST2GO v2.1.2)
- Colorful credits box showing author, website, and GitHub links
- Yellow "Usage" section, green "Options" and "Commands" headers
- Cyan command and option flags for better readability
- White descriptions with proper formatting
- **Website Integration**: Added official website link (https://ost2go.kief.fi) to help display
- Blue underlined links for website and GitHub
- Displayed prominently with emoji indicators (🌐 and 📂)
- **Credits Everywhere**: Credits box now appears on all help displays
- Shows on `--help`, `-h`, and when no arguments provided
- Enhanced with emojis: 👤 Author, 🌐 Website, 📂 GitHub, 📦 Project
- Colorized with cyan borders, green author name, and blue links
#### Changed
- Upgraded credits display from gray to colorful cyan-bordered box
- Improved help output formatting with better color coordination
- Enhanced branding visibility throughout CLI interface
#### Fixed
- Removed duplicate help message that appeared when running without arguments
- Fixed redundant help display in Commander.js setup
## [2.1.0] - 2025-10-17

@@ -10,0 +38,0 @@

+1
-1
{
"name": "ost2go",
"version": "2.1.1",
"version": "2.1.2",
"description": "A comprehensive Node.js application to convert, extract, and manage Microsoft Outlook OST files with UTF-8 support",

@@ -5,0 +5,0 @@ "main": "src/index.js",

@@ -17,2 +17,6 @@ # 🚀 OST2GO - ![Logo](https://i.imgur.com/htRNNnp.png)

[![Build Status](https://img.shields.io/github/actions/workflow/status/SkyLostTR/OST2GO/publish.yml?branch=main)](https://github.com/SkyLostTR/OST2GO/actions)
[![GitHub Forks](https://img.shields.io/github/forks/SkyLostTR/OST2GO.svg)](https://github.com/SkyLostTR/OST2GO/network/members)
[![Contributors](https://img.shields.io/github/contributors/SkyLostTR/OST2GO.svg)](https://github.com/SkyLostTR/OST2GO/graphs/contributors)
[![Last Commit](https://img.shields.io/github/last-commit/SkyLostTR/OST2GO.svg)](https://github.com/SkyLostTR/OST2GO/commits/main)
[![Code Size](https://img.shields.io/github/languages/code-size/SkyLostTR/OST2GO.svg)](https://github.com/SkyLostTR/OST2GO)

@@ -430,1 +434,2 @@ **Created by [SkyLostTR](https://github.com/SkyLostTR) (@Keeftraum)**

@@ -7,3 +7,3 @@ /**

* @author SkyLostTR (@Keeftraum)
* @version 2.0.0
* @version require('../package.json').version;
* @license SEE LICENSE IN LICENSE

@@ -22,9 +22,170 @@ * @repository https://github.com/SkyLostTR/OST2GO

const { PSTFile } = require('pst-extractor');
const packageInfo = require('../package.json');
const program = new Command();
/**
* Helper function to format ETA in seconds and minutes
* @param {number} eta - ETA in seconds
* @returns {string} Formatted ETA string
*/
function formatETA(eta) {
if (isNaN(eta) || eta === Infinity || eta < 0 || eta === 0) {
return '--';
}
const totalSeconds = Math.round(eta);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
if (minutes > 0) {
return `${minutes}m ${seconds}s`;
}
return `${seconds}s`;
}
/**
* Create a custom ProgressBar with formatted ETA in minutes and seconds
*/
function createProgressBar(format, options) {
// We'll manually manage the progress display
let bar = null;
let startTime = Date.now();
let lastRenderTime = 0;
const renderThrottle = options.renderThrottle || 16;
// Create a wrapper object that mimics ProgressBar interface
const wrapper = {
curr: 0,
total: options.total,
startTime: startTime,
tick: function(len = 1, tokens = {}) {
this.curr += len;
if (this.curr > this.total) this.curr = this.total;
// Throttle rendering
const now = Date.now();
if (now - lastRenderTime < renderThrottle && this.curr < this.total) {
return;
}
lastRenderTime = now;
// Calculate ETA
const elapsed = (now - startTime) / 1000;
const rate = this.curr / elapsed;
const remaining = this.total - this.curr;
const eta = remaining > 0 && rate > 0 ? remaining / rate : 0;
// Build the progress bar manually
const percent = Math.floor((this.curr / this.total) * 100);
const ratio = this.curr / this.total;
const width = options.width || 40;
const complete = Math.floor(width * ratio);
const incomplete = width - complete;
const completeChar = options.complete || '█';
const incompleteChar = options.incomplete || '░';
let bar = '[';
for (let i = 0; i < complete; i++) bar += completeChar;
for (let i = 0; i < incomplete; i++) bar += incompleteChar;
bar += ']';
// Build the display line
let line = format
.replace(':bar', bar)
.replace(':current', this.curr.toString())
.replace(':total', this.total.toString())
.replace(':percent', percent + '%');
// Add ETA
line += chalk.magenta(' ETA: ' + formatETA(eta));
// Clear line and write
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
process.stdout.write(line);
},
update: function(ratio, tokens = {}) {
this.curr = Math.floor(ratio * this.total);
this.tick(0, tokens);
},
terminate: function() {
process.stdout.write('\n');
}
};
return wrapper;
}
/**
* Display credits header for commands
*/
function showCredits() {
console.log(chalk.cyan('┌────────────────────────────────────────────────────────────┐'));
console.log(chalk.cyan('│ ') + chalk.bold.white('👤 Author: ') + chalk.green('SkyLostTR') + chalk.gray(' (@Keeftraum)') + chalk.cyan(' │'));
console.log(chalk.cyan('│ ') + chalk.bold.white('🌐 Website: ') + chalk.blue.underline('https://ost2go.kief.fi') + chalk.cyan(' │'));
console.log(chalk.cyan('│ ') + chalk.bold.white('📂 GitHub: ') + chalk.blue.underline('https://github.com/SkyLostTR/OST2GO') + chalk.cyan(' │'));
console.log(chalk.cyan('│ ') + chalk.bold.white('📦 Project: ') + chalk.magenta('OST2GO - OST/PST Management Toolkit') + chalk.cyan(' │'));
console.log(chalk.cyan('└────────────────────────────────────────────────────────────┘'));
}
// Get package.json data
// Override help display to show credits and colors
const originalOutputHelp = program.outputHelp.bind(program);
program.outputHelp = function(cb) {
// Display colorful header
console.log(chalk.bold.cyan('\n╔══════════════════════════════════════════════════════════════╗'));
console.log(chalk.bold.cyan('║') + chalk.bold.white(' 🚀 OST2GO v' + packageInfo.version + ' ') + chalk.bold.cyan('║'));
console.log(chalk.bold.cyan('╚══════════════════════════════════════════════════════════════╝\n'));
// Show credits
showCredits();
console.log('');
// Call original help but with color processing
return originalOutputHelp(function(str) {
// Remove the plain description line that comes after Usage
str = str.replace(/\n\n[A-Z].*OST files with UTF-8 support\n\n/g, '\n\n');
// Colorize different parts
str = str.replace(/(Usage:)(.+)/g, chalk.bold.yellow('$1') + chalk.white('$2'));
str = str.replace(/(Options:)/g, chalk.bold.green('$1'));
str = str.replace(/(Commands:)/g, chalk.bold.green('$1'));
// Colorize options (flags)
str = str.replace(/\s{2}(-[V-Zh-z]),\s(--[\w-]+)/g, ' ' + chalk.cyan('$1') + ', ' + chalk.cyan('$2'));
// Colorize command names at start of line
str = str.replace(/\n\s{2}(convert|info|extract|validate|help)(\s)/g, '\n ' + chalk.cyan('$1') + '$2');
// Colorize descriptions
str = str.replace(/(output the version number)/g, chalk.white('$1'));
str = str.replace(/(display help for command)/g, chalk.white('$1'));
str = str.replace(/(Convert OST file to PST format)/g, chalk.white('$1'));
str = str.replace(/(Display information about an OST file)/g, chalk.white('$1'));
str = str.replace(/(Extract emails from OST\/PST file to EML, MBOX, and JSON formats)/g, chalk.white('$1'));
str = str.replace(/(Validate PST file integrity and contents using pst-extractor library)/g, chalk.white('$1'));
// Add colorful description after Usage
const descLine = chalk.bold.cyan('OST2GO') + chalk.gray(' by ') + chalk.green('SkyLostTR') + chalk.gray(' (@Keeftraum)');
const desc = chalk.gray('📦 ') + chalk.white(packageInfo.description);
const website = chalk.gray('🌐 Website: ') + chalk.blue.underline('https://ost2go.kief.fi');
const github = chalk.gray('📂 GitHub: ') + chalk.blue.underline('https://github.com/SkyLostTR/OST2GO');
// Insert description after Usage line
str = str.replace(/(Usage:.+\n)\n/, `$1\n${descLine}\n${desc}\n${website}\n${github}\n\n`);
return str;
});
};
program
.name('ost2go')
.description('OST2GO by SkyLostTR (@Keeftraum) - Complete OST/PST management toolkit')
.version('2.0.0');
.description(packageInfo.description)
.version(packageInfo.version);

@@ -48,2 +209,3 @@ program

console.log(chalk.bold.cyan('╚══════════════════════════════════════════════════════════════╝'));
showCredits();

@@ -59,4 +221,4 @@ // Check if user wants real conversion

// Setup progress bar for real conversion
const progressBar = new ProgressBar(
chalk.cyan('🔄 Converting: ') + chalk.white('[:bar] ') + chalk.yellow(':current/:total ') + chalk.gray('(:percent)') + chalk.magenta(' ETA: :etas'),
const progressBar = createProgressBar(
chalk.cyan('🔄 Converting: ') + chalk.white('[:bar] ') + chalk.yellow(':current/:total ') + chalk.gray('(:percent)'),
{

@@ -174,4 +336,4 @@ total: 4, // 4 main steps

// Setup progress bar for legacy conversion
const progressBar = new ProgressBar(
chalk.cyan('🔄 Converting: ') + chalk.white('[:bar] ') + chalk.gray('(:percent)') + chalk.magenta(' ETA: :etas'),
const progressBar = createProgressBar(
chalk.cyan('🔄 Converting: ') + chalk.white('[:bar] ') + chalk.gray('(:percent)'),
{

@@ -245,2 +407,3 @@ total: 100, // Simulate progress for legacy converter

console.log(chalk.bold.cyan('╚══════════════════════════════════════════════════════════════╝'));
showCredits();

@@ -255,4 +418,4 @@ if (!await fs.pathExists(options.input)) {

// Setup progress bar for analysis
const progressBar = new ProgressBar(
chalk.cyan('📋 Analyzing: ') + chalk.white('[:bar] ') + chalk.gray('(:percent)') + chalk.magenta(' ETA: :etas'),
const progressBar = createProgressBar(
chalk.cyan('📋 Analyzing: ') + chalk.white('[:bar] ') + chalk.gray('(:percent)'),
{

@@ -341,2 +504,3 @@ total: 100,

console.log(chalk.bold.cyan('╚══════════════════════════════════════════════════════════════╝'));
showCredits();

@@ -376,4 +540,4 @@ const inputPath = path.resolve(options.input);

// Setup progress bar
const progressBar = new ProgressBar(
chalk.cyan('📧 Extracting: ') + chalk.white('[:bar] ') + chalk.yellow(':current/:total ') + chalk.gray('(:percent)') + chalk.magenta(' ETA: :etas'),
const progressBar = createProgressBar(
chalk.cyan('📧 Extracting: ') + chalk.white('[:bar] ') + chalk.yellow(':current/:total ') + chalk.gray('(:percent)'),
{

@@ -452,25 +616,74 @@ total: maxEmails,

let attachData = null;
let extractionMethod = 'none';
// Method 1: Try fileInputStream with improved block reading
try {
const stream = attach.fileInputStream;
if (stream) {
const chunks = [];
let totalSize = 0;
const bufferSize = 8176;
const buffer = Buffer.alloc(bufferSize);
try {
let bytesRead = stream.read(buffer);
while (bytesRead > 0) {
chunks.push(Buffer.from(buffer.slice(0, bytesRead)));
totalSize += bytesRead;
bytesRead = stream.read(buffer);
// First check if allData is available (already decompressed)
if (stream.allData && stream.allData.length > 0) {
attachData = stream.allData;
extractionMethod = 'stream-alldata';
}
// Check if we can read blocks directly (for problematic compressions)
else if (stream.indexItems && stream.indexItems.length > 0) {
const chunks = [];
let totalSize = 0;
const zlib = require('zlib');
for (const item of stream.indexItems) {
try {
const blockBuffer = Buffer.alloc(item.size);
attach.pstFile.seek(item.fileOffset);
attach.pstFile.readCompletely(blockBuffer);
// Check if zlib compressed
if (blockBuffer.length > 2 && blockBuffer[0] === 0x78 && blockBuffer[1] === 0x9c) {
try {
const decompressed = zlib.unzipSync(blockBuffer);
chunks.push(decompressed);
totalSize += decompressed.length;
} catch (err) {
// Decompression failed, use raw
chunks.push(blockBuffer);
totalSize += blockBuffer.length;
}
} else {
chunks.push(blockBuffer);
totalSize += blockBuffer.length;
}
} catch (blockErr) {
// Skip bad blocks
}
}
if (totalSize > 0) {
if (chunks.length > 0) {
attachData = Buffer.concat(chunks, totalSize);
extractionMethod = 'stream-blocks';
}
} catch (zlibErr) {
skippedAttachments++;
if (options.verbose) {
console.log(chalk.gray(` ⚠️ Skipping attachment ${i} (compression error)`));
}
// Fall back to normal stream reading
else {
const chunks = [];
let totalSize = 0;
const bufferSize = 8176;
const buffer = Buffer.alloc(bufferSize);
try {
let bytesRead = stream.read(buffer);
while (bytesRead > 0) {
chunks.push(Buffer.from(buffer.slice(0, bytesRead)));
totalSize += bytesRead;
bytesRead = stream.read(buffer);
}
if (totalSize > 0) {
attachData = Buffer.concat(chunks, totalSize);
extractionMethod = 'stream';
}
} catch (zlibErr) {
// Compression error - try alternative method
if (options.verbose) {
console.log(chalk.yellow(` ⚠️ Compression error on ${attach.longFilename || attach.filename || `attachment${i}`}, trying alternative method...`));
}
}

@@ -480,5 +693,111 @@ }

} catch (streamErr) {
// Stream error - try alternative method
if (options.verbose) {
console.log(chalk.yellow(` ⚠️ Stream error on ${attach.longFilename || attach.filename || `attachment${i}`}, trying alternative method...`));
}
}
// Method 2: Try direct property table access before decompression
if (!attachData) {
try {
// Try to get PidTagAttachDataBinary (0x3701) - the raw property
const dataProperty = attach.pstTableItems?.get(0x3701);
if (dataProperty) {
// If it's an external reference, we need to get the descriptor item
if (dataProperty.isExternalValueReference) {
const descriptorItem = attach.localDescriptorItems?.get(dataProperty.entryValueReference);
if (descriptorItem) {
// Try to read the raw data from the PST file
try {
// Get the offset item and read raw data
const Long = require('long');
const offsetId = Long.isLong(descriptorItem.offsetIndexIdentifier)
? descriptorItem.offsetIndexIdentifier
: Long.fromNumber(descriptorItem.offsetIndexIdentifier);
const offsetItem = attach.pstFile.getOffsetIndexNode(offsetId);
if (offsetItem) {
const rawBuffer = Buffer.alloc(offsetItem.size);
attach.pstFile.seek(offsetItem.fileOffset);
attach.pstFile.readCompletely(rawBuffer);
// Try to decompress manually with better error handling
if (rawBuffer.length > 2 && rawBuffer[0] === 0x78 && rawBuffer[1] === 0x9c) {
// This is zlib compressed
try {
const zlib = require('zlib');
attachData = zlib.unzipSync(rawBuffer);
extractionMethod = 'manual-zlib';
if (options.verbose) {
console.log(chalk.green(` ✅ Extracted ${attach.longFilename || attach.filename || `attachment${i}`} using manual zlib decompression (${attachData.length} bytes)`));
}
} catch (zlibManualErr) {
// Try inflateSync as alternative
try {
const zlib = require('zlib');
attachData = zlib.inflateSync(rawBuffer);
extractionMethod = 'manual-inflate';
if (options.verbose) {
console.log(chalk.green(` ✅ Extracted ${attach.longFilename || attach.filename || `attachment${i}`} using manual inflate (${attachData.length} bytes)`));
}
} catch (inflateErr) {
if (options.verbose) {
console.log(chalk.yellow(` ⚠️ Manual decompression failed, using raw compressed data (${rawBuffer.length} bytes)`));
}
// Use the raw compressed data as last resort
attachData = rawBuffer;
extractionMethod = 'raw-compressed';
}
}
} else {
// Not compressed, use as-is
attachData = rawBuffer;
extractionMethod = 'raw-uncompressed';
if (options.verbose && attachData.length > 0) {
console.log(chalk.green(` ✅ Extracted ${attach.longFilename || attach.filename || `attachment${i}`} using raw method (${attachData.length} bytes)`));
}
}
}
} catch (rawErr) {
if (options.verbose) {
console.log(chalk.gray(` ⚠️ Raw data extraction error: ${rawErr.message}`));
}
}
}
} else if (dataProperty.data) {
// Internal value reference
attachData = dataProperty.data;
extractionMethod = 'property-internal';
if (options.verbose) {
console.log(chalk.green(` ✅ Extracted ${attach.longFilename || attach.filename || `attachment${i}`} using internal property`));
}
}
}
} catch (propErr) {
if (options.verbose) {
console.log(chalk.gray(` ⚠️ Property method error: ${propErr.message}`));
}
}
}
// Method 3: Try attachDataBinary property
if (!attachData && attach.attachDataBinary) {
try {
attachData = attach.attachDataBinary;
extractionMethod = 'binary';
if (options.verbose) {
console.log(chalk.green(` ✅ Extracted ${attach.longFilename || attach.filename || `attachment${i}`} using binary method`));
}
} catch (binaryErr) {
if (options.verbose) {
console.log(chalk.gray(` ⚠️ Binary extraction failed`));
}
}
}
// If all methods failed
if (!attachData) {
skippedAttachments++;
if (options.verbose) {
console.log(chalk.gray(` ⚠️ Attachment ${i} stream error`));
console.log(chalk.red(` ❌ Failed to extract ${attach.longFilename || attach.filename || `attachment${i}`} - all methods failed`));
}

@@ -491,3 +810,4 @@ }

size: attach.attachSize || 0,
data: attachData
data: attachData,
extractionMethod: extractionMethod
});

@@ -498,3 +818,3 @@ }

if (options.verbose) {
console.log(chalk.gray(` ⚠️ Attachment ${i} error`));
console.log(chalk.red(` ❌ Attachment ${i} error: ${e.message}`));
}

@@ -671,2 +991,3 @@ }

console.log(chalk.bold.cyan('╚══════════════════════════════════════════════════════════════╝'));
showCredits();

@@ -688,4 +1009,4 @@ const inputPath = path.resolve(options.input);

// Setup progress bar for validation
const progressBar = new ProgressBar(
chalk.cyan('🔍 Validating: ') + chalk.white('[:bar] ') + chalk.gray('(:percent)') + chalk.magenta(' ETA: :etas'),
const progressBar = createProgressBar(
chalk.cyan('🔍 Validating: ') + chalk.white('[:bar] ') + chalk.gray('(:percent)'),
{

@@ -769,7 +1090,3 @@ total: 100,

// Show help when no command is provided
if (!process.argv.slice(2).length) {
program.outputHelp();
}
// Commander.js automatically shows help when no command is provided
program.parse(process.argv);