VulpesOS/gaia/shared/resources/apn/query.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

428 lines
14 KiB
JavaScript
Executable file

'use strict';
document.addEventListener('DOMContentLoaded', function onload() {
var DEBUG = false;
var OPERATOR_VARIANT_FILE = '../apn.json';
var GNOME_DB_FILE = 'service_providers.xml';
var ANDROID_DB_FILE = 'apns_conf.xml';
var LOCAL_ANDROID_DB_FILE = 'apns_conf_local.xml';
var OPERATOR_VARIANT_DB_FILE = 'operator_variant.xml';
var gGnomeDB = null;
var gAndroidDB = null;
var gOperatorVariantDB = null;
/**
* XML helpers: load & query XML databases
*/
function loadXML(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.send();
return xhr.responseXML;
}
function queryXML(xmlDocument, query) {
var xpe = new XPathEvaluator();
var nsResolver = xpe.createNSResolver(xmlDocument);
return xpe.evaluate(query, xmlDocument, nsResolver, 0, null);
}
/**
* Merge the Android and Gnome APN and the Operator Variant databases
*/
function queryAndroidDB(mcc, mnc) {
var query = '//apn';
if (mcc) {
query += '[@mcc=' + mcc + ']'; // Mobile Country Code
}
if (mnc) {
query += '[@mnc=' + mnc + ']'; // Mobile Network Code
}
var result = queryXML(gAndroidDB, query);
var res = result.iterateNext();
var found = [];
while (res) { // turn each resulting XML element into a JS object
var apn = {};
for (var i = 0; i < res.attributes.length; i++) {
var name = res.attributes[i].name;
var value = res.attributes[i].value;
if (name == 'type') { // array of comma-separated values
apn.type = value.split(',');
} else { // all other attributes are plain strings
apn[name] = value;
}
}
found.push(apn);
res = result.iterateNext();
}
return found;
}
function queryGnomeDB(mcc, mnc, setting) {
var query = '//gsm[network-id' + '[@mcc=' + mcc + '][@mnc=' + mnc + ']' +
']/' + setting;
var result = queryXML(gGnomeDB, query);
var node = result.iterateNext();
return node ? node.textContent : '';
}
function queryOperatorVariantDB(mcc, mnc) {
var query = '//operator' + '[@mcc=' + mcc + '][@mnc=' + mnc + ']';
var result = queryXML(gOperatorVariantDB, query);
var res = result.iterateNext();
var found = [];
while (res) { // turn each resulting XML element into a JS object
var operatorSettings = {};
for (var i = 0; i < res.attributes.length; i++) {
var name = res.attributes[i].name;
var value = res.attributes[i].value;
operatorSettings[name] = value;
}
found.push(operatorSettings);
res = result.iterateNext();
}
return found.length ? found[0] : null;
}
function padLeft(num, length) {
var r = String(num);
while (r.length < length) {
r = '0' + r;
}
return r;
}
function mergeDBs() {
var apn = {};
for (var mcc = 1; mcc < 999; mcc++) {
var country = {};
var _mcc = padLeft(mcc, 3);
var result = queryAndroidDB(_mcc);
if (result && result.length) {
result.sort();
for (var i = 0; i < result.length; i++) {
/* jshint loopfunc: true */
var mnc = result[i].mnc;
var operatorVariantSettings = {};
var voicemail = queryGnomeDB(_mcc, mnc, 'voicemail');
if (voicemail) {
operatorVariantSettings.voicemail = voicemail;
if (DEBUG) {
console.log(operatorVariantSettings.voicemail + ': ' + voicemail);
}
}
var otherSettings = queryOperatorVariantDB(_mcc, mnc);
if (otherSettings) {
if (DEBUG) {
console.log('Other operator settings: ' +
JSON.stringify(otherSettings));
}
if (!operatorVariantSettings.voicemail) {
voicemail = otherSettings.voicemail;
if (voicemail) {
operatorVariantSettings.voicemail = voicemail;
}
}
var enableStrict7BitEncodingForSms =
otherSettings.enableStrict7BitEncodingForSms;
if (enableStrict7BitEncodingForSms) {
operatorVariantSettings.enableStrict7BitEncodingForSms =
enableStrict7BitEncodingForSms == 'true';
}
var cellBroadcastSearchList =
otherSettings.cellBroadcastSearchList;
if (cellBroadcastSearchList) {
var searchListObj = {};
var lists = cellBroadcastSearchList.split('|');
lists.forEach(function(list) {
var parts = list.split(':');
searchListObj[parts[0]] = parts[1];
});
operatorVariantSettings.cellBroadcastSearchList = searchListObj;
}
var operatorSizeLimitation =
otherSettings.operatorSizeLimitation;
if (operatorSizeLimitation) {
operatorVariantSettings.operatorSizeLimitation =
+operatorSizeLimitation;
}
var skipProxy = otherSettings.skipProxy;
if (skipProxy == 'true') {
var skipProxyFor = otherSettings.skipProxyFor;
if (skipProxyFor) {
if (skipProxyFor.indexOf(result[i].carrier) != -1) {
if (DEBUG) {
console.log('Skip proxy setting for: ' + result[i].carrier);
}
result[i].proxy = result[i].port = '';
}
}
}
}
delete(result[i].mcc);
delete(result[i].mnc);
if (country[mnc]) {
if (DEBUG) { // warn about the duplicate (mcc, mnc) tuple
if (country[mnc].length == 1) {
console.log('duplicate mcc/mnc: ' + _mcc + '/' + mnc);
console.log(' - ' + country[mnc][0].carrier);
}
console.log(' - ' + result[i].carrier);
}
country[mnc].push(result[i]);
} else {
country[mnc] = [result[i]];
if (voicemail || otherSettings) {
operatorVariantSettings.type = [];
operatorVariantSettings.type.push('operatorvariant');
country[mnc].push(operatorVariantSettings);
}
}
}
apn[_mcc] = country;
}
}
return apn;
}
/**
* User Interface
*/
var gAPN;
var apnPrefNames = {
'default': {
'ril.data.carrier': 'carrier',
'ril.data.apn': 'apn',
'ril.data.user': 'user',
'ril.data.passwd': 'password',
'ril.data.httpProxyHost': 'proxy',
'ril.data.httpProxyPort': 'port',
'ril.data.authtype': 'authtype',
'ril.data.mtu': 'mtu'
},
'supl': {
'ril.supl.carrier': 'carrier',
'ril.supl.apn': 'apn',
'ril.supl.user': 'user',
'ril.supl.passwd': 'password',
'ril.supl.httpProxyHost': 'proxy',
'ril.supl.httpProxyPort': 'port',
'ril.supl.authtype': 'authtype',
'ril.supl.mtu': 'mtu'
},
'mms': {
'ril.mms.carrier': 'carrier',
'ril.mms.apn': 'apn',
'ril.mms.user': 'user',
'ril.mms.passwd': 'password',
'ril.mms.httpProxyHost': 'proxy',
'ril.mms.httpProxyPort': 'port',
'ril.mms.mmsc': 'mmsc',
'ril.mms.mmsproxy': 'mmsproxy',
'ril.mms.mmsport': 'mmsport',
'ril.mms.authtype': 'authtype',
'ril.mms.mtu': 'mtu'
},
'dun': {
'ril.dun.carrier': 'carrier',
'ril.dun.apn': 'apn',
'ril.dun.user': 'user',
'ril.dun.passwd': 'password',
'ril.dun.httpProxyHost': 'proxy',
'ril.dun.httpProxyPort': 'port',
'ril.dun.authtype': 'authtype',
'ril.dun.mtu': 'mtu'
},
'ims': {
'ril.ims.carrier': 'carrier',
'ril.ims.apn': 'apn',
'ril.ims.user': 'user',
'ril.ims.passwd': 'password',
'ril.ims.httpProxyHost': 'proxy',
'ril.ims.httpProxyPort': 'port',
'ril.ims.authtype': 'authtype',
'ril.ims.mtu': 'mtu'
},
'fota': {
'ril.fota.carrier': 'carrier',
'ril.fota.apn': 'apn',
'ril.fota.user': 'user',
'ril.fota.passwd': 'password',
'ril.fota.httpProxyHost': 'proxy',
'ril.fota.httpProxyPort': 'port',
'ril.fota.authtype': 'authtype',
'ril.fota.mtu': 'mtu'
},
'operatorvariant': {
'ril.iccInfo.mbdn': 'voicemail',
'ril.sms.strict7BitEncoding.enabled': 'enableStrict7BitEncodingForSms',
'ril.cellbroadcast.searchlist': 'cellBroadcastSearchList',
'dom.mms.operatorSizeLimitation': 'operatorSizeLimitation'
}
};
var booleanPrefNames = [
'ril.sms.strict7BitEncoding.enabled'
];
function loadDB(output, callback) {
output.textContent = '\n loading database...';
var xhr = new XMLHttpRequest();
xhr.open('GET', OPERATOR_VARIANT_FILE, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status === 0) {
// the JSON database is already available -- we don't use
// xhr.responseType = 'json' to be compatible with Chrome... sigh.
gAPN = JSON.parse(xhr.responseText);
} else {
// the JSON database is not available, merge the three XML databases
output.textContent = '\n merging databases, this takes a while...';
gAndroidDB = loadXML(ANDROID_DB_FILE);
// First merge the local DB
var localAndroidDB = loadXML(LOCAL_ANDROID_DB_FILE);
var localApns =
localAndroidDB.documentElement.querySelectorAll('apn');
var androidApns;
var androidApn;
for (var localApn of localApns) {
if (localApn.getAttribute('overwrite') &&
localApn.getAttribute('carrier_name')) {
var pattern = '[carrier="' +
localApn.getAttribute('carrier_name') + '"]';
androidApns =
gAndroidDB.documentElement.querySelectorAll(pattern);
var matchFound = false;
for (androidApn of androidApns) {
if (androidApn &&
androidApn.getAttribute('carrier') ===
localApn.getAttribute('carrier_name')) {
if (DEBUG) {
console.log('- replace "' +
androidApn.getAttribute('apn') +
'" to "' + localApn.getAttribute('apn') +
'"');
}
localApn.removeAttribute('overwrite');
localApn.removeAttribute('carrier_name');
var parent = androidApn.parentNode;
parent.insertBefore(localApn, androidApn);
parent.removeChild(androidApn);
matchFound = true;
}
}
if (!matchFound) {
alert('Warning!\nCould not find APN \'' +
localApn.getAttribute('carrier_name') +
'\'. Which is expected to be overwrited.');
}
continue;
}
// use local apn to patch origin carrier name in the Android DB
// if the name is not the correct one (see bug 863126).
// Note: This patch will not function once we get
// the correct names updated in the upstream database.
if (localApn.getAttribute('name')) {
var localPattern = 'apn' +
'[mcc="' + localApn.getAttribute('mcc') + '"]' +
'[mnc="' + localApn.getAttribute('mnc') + '"]';
androidApns =
gAndroidDB.documentElement.querySelectorAll(localPattern);
for (androidApn of androidApns) {
if (androidApn &&
androidApn.getAttribute('carrier') ===
localApn.getAttribute('carrier')) {
if (DEBUG) {
console.log('- replace "' +
androidApn.getAttribute('carrier') +
'" to "' + localApn.getAttribute('name') +
'"');
}
androidApn.setAttribute('carrier',
localApn.getAttribute('name'));
}
}
} else {
gAndroidDB.documentElement.appendChild(localApn);
}
}
// Then the Gnome DB
gGnomeDB = loadXML(GNOME_DB_FILE);
gOperatorVariantDB = loadXML(OPERATOR_VARIANT_DB_FILE);
gAPN = mergeDBs();
}
output.textContent = DEBUG ?
JSON.stringify(gAPN, true, 2) :
JSON.stringify(gAPN, true, 0)
.replace(/("[0-9]+"):{/g, '\n$1: {')
.replace(/("[0-9]+"):\[/g, '\n $1: [')
.replace(/},\n/g, '\n},\n')
.replace(/\[{/g, '[\n {')
.replace(/},{/g, '},\n {')
.replace(/}(],?)/g, '}\n $1')
.replace(/]}}$/, ']\n}\n}');
callback();
}
};
xhr.send();
}
function update() {
var selection = document.getElementById('selection');
var mcc = document.querySelector('input[name=mcc]').value;
var mnc = document.querySelector('input[name=mnc]').value;
var res = gAPN[mcc] ? (gAPN[mcc][mnc] || []) : [];
selection.textContent = JSON.stringify(res, true, 2);
var preferences = document.getElementById('preferences');
var prefs = {};
for (var type in apnPrefNames) {
var apn = {};
for (var i = 0; i < res.length; i++) {
if (res[i].type.indexOf(type) != -1) {
apn = res[i];
break;
}
}
var prefNames = apnPrefNames[type];
for (var key in prefNames) {
var name = apnPrefNames[type][key];
if (booleanPrefNames.indexOf(key) != -1) {
prefs[key] = apn[name] || false;
} else {
prefs[key] = apn[name] || '';
}
}
}
preferences.textContent = JSON.stringify(prefs, true, 2);
}
document.querySelector('form').oninput = update;
loadDB(document.querySelector('textarea'), update);
});