-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfix-maildir-size.js
executable file
·129 lines (100 loc) · 3.67 KB
/
fix-maildir-size.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/node --harmony-async-await
const fs = require('mz/fs');
const zlib = require('mz/zlib');
const path = require('path');
const colors = require('colors');
const status = require('node-status');
const GZIP_MAGIC = '1f8b';
const MAILDIR_MAGIC = ':2,';
const resolvePath = (...parts) => path.resolve(process.cwd(), 'cur', ...parts);
const isGzipCompressed = (buffer) => buffer.slice(0, 2).toString('hex') === GZIP_MAGIC;
const getRawSize = (buffer) => buffer.readInt32LE(buffer.length-4);
const isTwiceCompressed = async (buffer) => {
try {
if(isGzipCompressed(buffer)) {
const unzipped = await zlib.unzip(buffer);
const stillZipped = isGzipCompressed(unzipped);
if(stillZipped) {
return [ true, unzipped ];
}
}
}
catch(e) { }
return [ false ];
};
status.start({
pattern: 'Processing files {spinner} | {files} {files.bar} | Unsized: {unsized} | Invalid: {invalid} | Twice-compressed: {twice}'
});
(async () => {
const files = await fs.readdir(resolvePath());
const skip = parseInt(process.argv[2], 10) || 0;
const progress = status.addItem('files', { max: files.length, count: skip });
const invalid = status.addItem('invalid');
const twice = status.addItem('twice');
const unsized = status.addItem('unsized');
for(var file of files.slice(skip)) {
const console = status.console();
progress.inc();
const filePath = resolvePath(file);
// console.log(`Processing ${filePath}`);
const [bbase, fflags] = file.split(MAILDIR_MAGIC);
const [base, ...sizes] = bbase.split(',');
const flags = fflags.split(',');
const sizesObj = sizes.reduce((acc, i) => {
const [k,v] = i.split('=');
return Object.assign(acc, { [k]: parseInt(v, 10) });
}, {});
if(!sizesObj.S) {
unsized.inc();
continue;
}
const stat = await fs.stat(filePath);
// Only need to check if size doesn't match filesize
if(stat.size === sizesObj.S) { continue; }
const buffer = await fs.readFile(filePath);
const { mode, atime, mtime, uid, gid } = stat;
const isGzip = isGzipCompressed(buffer);
const [twiceCompressed, unzipped] = await isTwiceCompressed(buffer);
const newFlags = flags.filter(f => f !== 'Z').join(',');
const rawSize = isGzip ? getRawSize(buffer) : stat.size;
if(sizesObj.S !== rawSize) {
invalid.inc();
if(twiceCompressed) {
twice.inc();
console.log(`File ${filePath} is compressed twice`.red);
const newSize = `,S=${getRawSize(unzipped)}`;
const fileName = `${base}${newSize}${MAILDIR_MAGIC}${newFlags}`;
const tmpFile = resolvePath('../tmp', fileName);
try {
await fs.writeFile(tmpFile, unzipped);
await fs.utimes(tmpFile, atime, mtime);
await fs.chown(tmpFile, uid, gid);
await fs.unlink(filePath);
await fs.rename(tmpFile, resolvePath(fileName));
}
catch(ex) {
console.error(ex);
process.exit(2);
}
continue;
}
console.log(`${isGzip ? 'C' : 'Unc'}ompressed ${file} size of ${rawSize} doesn't match flags ${sizesObj.S}`);
const newSize = `,S=${rawSize}`;
const fileName = `${base}${newSize}${MAILDIR_MAGIC}${newFlags}`;
const tmpName = resolvePath(fileName);
try {
await fs.rename(filePath, tmpName);
await fs.utimes(tmpName, atime, mtime);
await fs.chown(tmpName, uid, gid);
}
catch(ex) {
console.error(ex);
process.exit(3);
}
}
}
setTimeout(() => {
status.stop();
console.log('\r\nDone');
}, 250);
})().catch(ex => { console.error(ex); process.exit(1); });