Check paths length
// ==UserScript==
// @name Validate .torrent filepaths
// @version 1.0
// @author Anakunda
// @description Check paths length
// @match https://redacted.ch/upload.php*
// @match https://notwhat.cd/upload.php*
// @match https://orpheus.network/upload.php*
// @namespace https://greasyfork.org/users/321857
// ==/UserScript==
(function() {
'use strict';
let file = document.getElementById('file');
if (file == null) return;
class HTML extends String { };
var tfMessages = [ ];
String.prototype.trueLength = function() {
return this.normalize('NFC').length;
// var index = 0, width = 0, len = 0;
// while (index < this.length) {
// var point = this.codePointAt(index);
// width = 0;
// while (point) {
// ++width;
// point = point >> 8;
// }
// index += Math.round(width / 2);
// ++len;
// }
// return len;
};
document.head.appendChild(document.createElement('style')).innerHTML = `
.ua-messages {
text-indent: -2em;
margin-left: 2em;
font: 8pt Verdana, Tahoma, sans-serif;
}
.ua-messages-bg {
padding: 15px;
text-align: left;
background-color: darkslategray;
}
.ua-critical { color: red; font-weight: bold; font-size: 10pt; }
.ua-warning { color: #ff8d00; font-weight: 500; font-size: 9pt; }
.ua-notice { color: #e3d67b; }
.ua-info { color: white; }
`;
function addMessage(text, cls) {
if (!cls) return null;
switch (cls = cls.toLowerCase()) {
case 'info': var prefix = 'Info'; break;
case 'notice': prefix = 'Notice'; break;
case 'warning': prefix = 'Warning'; break;
case 'critical': prefix = 'FATAL'; break;
default:
console.warn('addMessage(...) invalid param:', cls);
return null;
}
let td = document.querySelector('tr#ual-messages > td');
if (td == null) {
let tr = document.createElement('tr');
tr.id = 'ual-messages';
td = document.createElement('td');
td.colSpan = 2;
td.className = 'ua-messages-bg';
tr.append(td);
file.parentElement.parentElement.insertAdjacentElement('afterend', tr);
}
let div = document.createElement('div');
div.classList.add('ua-messages', 'ua-' + cls);
div[text instanceof HTML ? 'innerHTML' : 'textContent'] = prefix + ': ' + text;
return td.appendChild(div);
}
function safeText(unsafeText) {
let div = document.createElement('div');
div.innerText = unsafeText || '';
return div.innerHTML;
}
function validateTorrentFile(torrent) {
function bdecode(str) {
let pos = 0, infoBegin = 0, infoEnd = 0;
return bdecodeInternal(str);
function bdecodeInternal(str) {
if (pos >= str.length) return null;
switch (str[pos]) {
case 100: // char code for 'd'
++pos;
var retval = [];
while (str[pos] != 101) { // char code for 'e'
var key = bdecodeInternal(str), val = bdecodeInternal(str);
if (key === null || val === null) break;
retval[key] = val;
}
if (infoEnd == -1) infoEnd = pos + 1;
retval.isDct = true;
++pos;
return retval;
case 105: // char code for 'i'
++pos;
var digits = Array.prototype.indexOf.call(str, 101, pos) - pos; // 101 = char code for 'e'
val = '';
for (var i = pos; i < digits + pos; ++i) val += String.fromCharCode(str[i]);
val = Math.round(parseFloat(val));
pos += digits + 1;
return val;
case 108: // char code for 'l'
++pos;
retval = [];
while (str[pos] != 101) { // char code for 'e'
let val = bdecodeInternal(str);
if (val === null) break;
retval.push(val);
}
++pos;
return retval;
default: {
digits = Array.prototype.indexOf.call(str, 58, pos) - pos; // 58 = char code for ':'
if (digits < 0 || digits > 20) return null;
let len = '';
for (i = pos; i < digits + pos; ++i) len += String.fromCharCode(str[i]);
len = parseInt(len);
pos += digits + 1;
let fstring = '';
for (i = pos; i < len + pos; ++i) fstring += String.fromCharCode(str[i]);
pos += len;
if (fstring == 'info') {
infoBegin = pos;
infoEnd = -1;
}
return fstring;
}
}
}
}
tfMessages.forEach(elem => { elem.remove() });
tfMessages = [ ];
let fr = new FileReader;
fr.onload = function(evt) {
torrent = bdecode(new Uint8Array(fr.result));
if (!torrent || typeof torrent != 'object') {
console.warn('Assertion failed:', torrent);
return;
}
const maxPathLen = 180;
let rootImageCount = 0, category = document.getElementById('categories'),
rootFolderName = decodeURIComponent(escape(torrent.info.name));
torrent.info.files.forEach(function(file) {
let fullPath = decodeURIComponent(escape(file.path.join('/')));
if (/\s{2,}/.test(fullPath))
tfMessages.push(addMessage('excessive whitespace in file path: ' + fullPath, 'warning'));
if (file.path.some(folderName => /^\s+|\s+$/.test(folderName)))
tfMessages.push(addMessage('leading/tailing whitespace in path component: ' + fullPath, 'warning'));
let fileName = decodeURIComponent(escape(file.path.pop())),
totalLen = rootFolderName.trueLength() + 1 + fullPath.trueLength();
if (totalLen > maxPathLen) tfMessages.push(addMessage(new HTML('file "' +
safeText(fullPath.normalize('NFC').slice(0, Math.max(maxPathLen - 1 - rootFolderName.trueLength(), 0))) +
safeText(fullPath.normalize('NFC').slice(Math.max(maxPathLen - 1 - rootFolderName.trueLength(), 0))).bold() +
'" exceeding allowed length (' + totalLen + ' > ' + maxPathLen + ')'), 'warning'));
if (/(?:\.(?:torrent|\!ut|\!qb|url|lnk|tmp|bak)|^Thumbs\.db)$/i.test(fileName))
tfMessages.push(addMessage(new HTML('garbage file "' + safeText(fullPath).bold() + '"'), 'warning'));
});
let ref = document.querySelector('td.ua-messages-bg');
if (ref != null && ref.childElementCount <= 0) ref.parentNode.remove();
};
fr.onerror = fr.ontimeout = error => { console.error('FileReader error (' + torrent.name + ')') };
fr.readAsArrayBuffer(torrent);
}
file.oninput = function(evt) {
if (evt.target.files.length > 0) validateTorrentFile(evt.target.files[0]);
};
if (file.files.length > 0) validateTorrentFile(file.files[0]);
})();