mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-30 00:25:49 +00:00
Merge pull request #572 from mDuo13/xrpltoml_validator
xrp-ledger.toml Checker
This commit is contained in:
22
assets/js/ripple-lib-1.1.2-min.js
vendored
22
assets/js/ripple-lib-1.1.2-min.js
vendored
File diff suppressed because one or more lines are too long
15
assets/js/ripple-lib-1.2.4-min.js
vendored
Normal file
15
assets/js/ripple-lib-1.2.4-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,117 +0,0 @@
|
||||
function VerificationError(message, tips) {
|
||||
this.name = "VerificationError";
|
||||
this.message = message || "";
|
||||
this.tips = tips || "";
|
||||
}
|
||||
VerificationError.prototype = Error.prototype;
|
||||
|
||||
function makeLogEntry(text) {
|
||||
var log = $('<li></li>').text(text).appendTo('#log');
|
||||
log.resolve = function(text) {
|
||||
return $('<span></span>').html(text).appendTo(log);
|
||||
};
|
||||
return log;
|
||||
}
|
||||
|
||||
$('#domain-entry').submit(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var domain = $('#domain').val(),
|
||||
txtdata;
|
||||
|
||||
$('.result-title').show();
|
||||
$('#result').show();
|
||||
$('#log').empty();
|
||||
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
var urls = [
|
||||
//'http://www.'+domain+'/ripple.txt',
|
||||
'https://www.' + domain + '/ripple.txt',
|
||||
//'http://'+domain+'/ripple.txt',
|
||||
'https://' + domain + '/ripple.txt',
|
||||
'https://ripple.' + domain + '/ripple.txt'
|
||||
].reverse();
|
||||
|
||||
function next(xhr, status) {
|
||||
if (this instanceof $) {
|
||||
var err;
|
||||
switch (status) {
|
||||
case 'timeout':
|
||||
err = 'TIMEOUT';
|
||||
break;
|
||||
case 'abort':
|
||||
err = 'ABORTED';
|
||||
break;
|
||||
case 'error':
|
||||
err = 'ERROR';
|
||||
break;
|
||||
default:
|
||||
err = 'UNKNOWN';
|
||||
}
|
||||
$('<span></span>').text(err).addClass('red').appendTo(this);
|
||||
}
|
||||
if (!urls.length) {
|
||||
var tips = 'Check if the file is actually hosted at one of the URLs above and make sure your server provides the required <a href="https://ripple.com/wiki/Ripple.txt#Publishing_ripple.txt">CORS header.</a>';
|
||||
callback(new VerificationError('No ripple.txt found!', tips));
|
||||
return;
|
||||
}
|
||||
var url = urls.pop();
|
||||
var log = makeLogEntry('Checking ' + url + '...');
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: 'text',
|
||||
success: function(data) {
|
||||
$('<span></span>').text('FOUND').addClass('green').appendTo(log);
|
||||
txtdata = data;
|
||||
callback();
|
||||
},
|
||||
error: $.proxy(next, log)
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
function() {
|
||||
txtdata = txtdata.replace('\r\n', '\n');
|
||||
txtdata = txtdata.replace('\r', '\n');
|
||||
txtdata = txtdata.split('\n');
|
||||
|
||||
var currentSection = "",
|
||||
sections = {};
|
||||
for (var i = 0, l = txtdata.length; i < l; i++) {
|
||||
var line = txtdata[i].replace(/^\s+|\s+$/g, '');
|
||||
if (!line.length || line[0] === '#') {
|
||||
continue;
|
||||
} else if (line[0] === '[' && line[line.length - 1] === ']') {
|
||||
currentSection = line.slice(1, line.length - 1);
|
||||
sections[currentSection] = [];
|
||||
} else {
|
||||
line = line.replace(/^\s+|\s+$/g, '');
|
||||
if (sections[currentSection]) {
|
||||
sections[currentSection].push(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var log;
|
||||
|
||||
log = makeLogEntry('Domain should match [domain]...');
|
||||
if (sections.domain && sections.domain.length && sections.domain[0] === domain) {
|
||||
log.resolve('VALID').addClass('green');
|
||||
} else if (sections.domain && sections.domain.length) {
|
||||
log.resolve('MISMATCH').addClass('red');
|
||||
} else {
|
||||
log.resolve('MISSING').addClass('red');
|
||||
}
|
||||
}
|
||||
], function(err, result) {
|
||||
if (err instanceof VerificationError) {
|
||||
var log = makeLogEntry("");
|
||||
log.resolve(err.message).addClass('red');
|
||||
$('<br>').appendTo(log);
|
||||
$('<p></p>').html(err.tips).appendTo(log);
|
||||
} else if (err) {
|
||||
makeLogEntry(err).addClass('red');
|
||||
}
|
||||
});
|
||||
});
|
||||
243
assets/js/xrp-ledger-toml-checker.js
Normal file
243
assets/js/xrp-ledger-toml-checker.js
Normal file
@@ -0,0 +1,243 @@
|
||||
const TOML_PATH = "/.well-known/xrp-ledger.toml"
|
||||
const TIPS = '<p>Check if the file is actually hosted at the URL above, check your server\'s HTTPS settings and certificate, and make sure your server provides the required <a href="xrp-ledger-toml.html#cors-setup">CORS header.</a></p>'
|
||||
const CLASS_GOOD = "badge badge-success"
|
||||
const CLASS_BAD = "badge badge-danger"
|
||||
|
||||
const ACCOUNT_FIELDS = [
|
||||
"address",
|
||||
"network",
|
||||
"desc"
|
||||
]
|
||||
const VALIDATOR_FIELDS = [
|
||||
"public_key",
|
||||
"network",
|
||||
"owner_country",
|
||||
"server_country",
|
||||
"unl"
|
||||
]
|
||||
const PRINCIPAL_FIELDS = [
|
||||
"name",
|
||||
"email"
|
||||
]
|
||||
const SERVER_FIELDS = [
|
||||
"json_rpc",
|
||||
"ws",
|
||||
"peer",
|
||||
"network"
|
||||
]
|
||||
const CURRENCY_FIELDS = [
|
||||
"code",
|
||||
"display_decimals",
|
||||
"issuer",
|
||||
"network",
|
||||
"symbol"
|
||||
]
|
||||
|
||||
function VerificationError(message, tips) {
|
||||
this.name = "VerificationError"
|
||||
this.message = message || ""
|
||||
this.tips = tips || ""
|
||||
}
|
||||
VerificationError.prototype = Error.prototype
|
||||
|
||||
function makeLogEntry(text, raw) {
|
||||
let log
|
||||
if (raw) {
|
||||
log = $('<li></li>').appendTo('#log').append(text)
|
||||
} else {
|
||||
log = $('<li></li>').text(text+" ").appendTo('#log')
|
||||
}
|
||||
log.resolve = function(text) {
|
||||
return $('<span></span>').html(text).appendTo(log)
|
||||
}
|
||||
return log
|
||||
}
|
||||
|
||||
function fetch_file() {
|
||||
const domain = $('#domain').val()
|
||||
const url = "https://" + domain + TOML_PATH
|
||||
|
||||
const log = makeLogEntry('Checking ' + url + '...')
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: 'text',
|
||||
success: function(data) {
|
||||
log.resolve('FOUND').addClass(CLASS_GOOD)
|
||||
parse_xrpl_toml(data, domain)
|
||||
},
|
||||
error: function(jqxhr, status, error) {
|
||||
switch (status) {
|
||||
case 'timeout':
|
||||
err = 'TIMEOUT'
|
||||
break
|
||||
case 'abort':
|
||||
err = 'ABORTED'
|
||||
break
|
||||
case 'error':
|
||||
err = 'ERROR'
|
||||
break
|
||||
default:
|
||||
err = 'UNKNOWN'
|
||||
}
|
||||
log.resolve(err).addClass(CLASS_BAD).after(TIPS)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function parse_xrpl_toml(data, domain) {
|
||||
let parsed
|
||||
let log1 = makeLogEntry("Parsing TOML data...")
|
||||
try {
|
||||
parsed = TOML(data)
|
||||
log1.resolve("SUCCESS").addClass(CLASS_GOOD)
|
||||
} catch(e) {
|
||||
log1.resolve(e).addClass(CLASS_BAD)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(parsed)
|
||||
|
||||
if (parsed.hasOwnProperty("METADATA")) {
|
||||
const metadata_type = makeLogEntry("Metadata section: ")
|
||||
if (Array.isArray(parsed.METADATA)) {
|
||||
metadata_type.resolve("Wrong type - should be table").addClass(CLASS_BAD)
|
||||
} else {
|
||||
metadata_type.resolve("Found").addClass(CLASS_GOOD)
|
||||
|
||||
if (parsed.METADATA.modified) {
|
||||
const mod_log = makeLogEntry("Modified date: ")
|
||||
try {
|
||||
mod_log.resolve(parsed.METADATA.modified.toISOString()).addClass(CLASS_GOOD)
|
||||
} catch(e) {
|
||||
mod_log.resolve("INVALID").addClass(CLASS_BAD)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function list_entries(name, list, fields, validate) {
|
||||
let list_wrap = $("<p>"+name+"</p>")
|
||||
let list_ol = $("<ol>").appendTo(list_wrap)
|
||||
for (i=0; i<list.length; i++) {
|
||||
let entry_wrap = $("<li>").appendTo(list_ol)
|
||||
let entry_def = $("<ul class='mb-3'>").appendTo(entry_wrap)
|
||||
let entry = list[i]
|
||||
for (j=0; j<fields.length; j++) {
|
||||
let fieldname = fields[j]
|
||||
if (entry[fieldname] !== undefined) {
|
||||
let field_def = $("<li><strong>"+fieldname+": </strong>").appendTo(entry_def)
|
||||
$(" <span class='"+fieldname+"'>").text(entry[fieldname]).appendTo(field_def)
|
||||
}
|
||||
}
|
||||
if (validate) {
|
||||
validate(entry).then((validated) => {
|
||||
if (validated === true) {
|
||||
entry_def.append('<li class="badge badge-success">Domain Validated <i class="fa fa-check-circle"></i></li>')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
makeLogEntry(list_wrap, true)
|
||||
}
|
||||
|
||||
if (parsed.ACCOUNTS) {
|
||||
if (!Array.isArray(parsed.ACCOUNTS)) {
|
||||
makeLogEntry("Accounts:").resolve("Wrong type - should be table-array").addClass(CLASS_BAD)
|
||||
} else {
|
||||
list_entries("Accounts:", parsed.ACCOUNTS, ACCOUNT_FIELDS, async function(acct) {
|
||||
if (acct.address === undefined) {return undefined}
|
||||
let net
|
||||
if (acct.network === undefined) { net = "main" } else { net = acct.network }
|
||||
return await validate_address_domain_on_net(acct.address, domain, net)
|
||||
})
|
||||
}
|
||||
}
|
||||
if (parsed.VALIDATORS) {
|
||||
if (!Array.isArray(parsed.VALIDATORS)) {
|
||||
makeLogEntry("Validators:").resolve("Wrong type - should be table-array").addClass(CLASS_BAD)
|
||||
} else {
|
||||
list_entries("Validators:", parsed.VALIDATORS, VALIDATOR_FIELDS)
|
||||
}
|
||||
}
|
||||
if (parsed.PRINCIPALS) {
|
||||
if (!Array.isArray(parsed.PRINCIPALS)) {
|
||||
makeLogEntry("Principals:").resolve("Wrong type - should be table-array").addClass(CLASS_BAD)
|
||||
} else {
|
||||
list_entries("Principals:", parsed.PRINCIPALS, PRINCIPAL_FIELDS)
|
||||
}
|
||||
}
|
||||
if (parsed.SERVERS) {
|
||||
if (!Array.isArray(parsed.SERVERS)) {
|
||||
makeLogEntry("Servers:").resolve("Wrong type - should be table-array").addClass(CLASS_BAD)
|
||||
} else {
|
||||
list_entries("Servers:", parsed.SERVERS, SERVER_FIELDS)
|
||||
}
|
||||
}
|
||||
if (parsed.CURRENCIES) {
|
||||
if (!Array.isArray(parsed.CURRENCIES)) {
|
||||
makeLogEntry("Currencies:").resolve("Wrong type - should be table-array").addClass(CLASS_BAD)
|
||||
} else {
|
||||
list_entries("Currencies:", parsed.CURRENCIES, CURRENCY_FIELDS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const testnet = new ripple.RippleAPI({server: 'wss://s.altnet.rippletest.net:51233'})
|
||||
testnet.connect()
|
||||
const mainnet = new ripple.RippleAPI({server: 'wss://s1.ripple.com'})
|
||||
mainnet.connect()
|
||||
|
||||
async function validate_address_domain_on_net(address, domain, net) {
|
||||
if (net === "main") {
|
||||
let settings
|
||||
try {
|
||||
settings = await mainnet.getSettings(address)
|
||||
} catch(e) {
|
||||
console.error("failed to look up address on mainnet:", address, e)
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (settings.domain === domain) {
|
||||
return true
|
||||
} else if (settings.domain === undefined) {
|
||||
console.debug(address, ": Domain is undefined in settings")
|
||||
return undefined
|
||||
} else {
|
||||
console.debug(address, ": Domain mismatch ("+settings.domain+" vs. "+domain+")")
|
||||
return false
|
||||
}
|
||||
} else if (net === "testnet") {
|
||||
let settings
|
||||
try {
|
||||
settings = await testnet.getSettings(address)
|
||||
} catch(e) {
|
||||
console.error("failed to look up address on testnet:", address, e)
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (settings.domain === domain) {
|
||||
return true
|
||||
} else if (settings.domain === undefined) {
|
||||
console.debug(address, ": Domain is undefined in settings")
|
||||
return undefined
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function handle_submit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
$('.result-title').show()
|
||||
$('#result').show()
|
||||
$('#log').empty()
|
||||
|
||||
fetch_file()
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
$('#domain-entry').submit(handle_submit)
|
||||
})
|
||||
Reference in New Issue
Block a user