VulpesOS/gaia/dev_apps/upnp-test/js/lib/plug.play/plug.upnp.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

1049 lines
30 KiB
JavaScript
Executable file

/* Copyright (c) 2012 Opera Software ASA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
!(function( global, undefined ) {
// *** rsvp.js (https://github.com/tildeio/rsvp.js)
(function(exports) { "use strict";
var MutationObserver = global.MutationObserver || global.WebKitMutationObserver;
var async;
if (typeof process !== 'undefined') {
async = function(callback, binding) {
process.nextTick(function() {
callback.call(binding);
});
};
} else if (MutationObserver) {
var queue = [];
var observer = new MutationObserver(function() {
var toProcess = queue.slice();
queue = [];
toProcess.forEach(function(tuple) {
var callback = tuple[0], binding = tuple[1];
callback.call(binding);
});
});
var element = global.document.createElement('div');
observer.observe(element, { attributes: true });
async = function(callback, binding) {
queue.push([callback, binding]);
element.setAttribute('drainQueue', 'drainQueue');
};
} else {
async = function(callback, binding) {
global.setTimeout(function() {
callback.call(binding);
}, 1);
};
}
exports.async = async;
var Event = exports.Event = function(type, options) {
this.type = type;
for (var option in options) {
if (!options.hasOwnProperty(option)) { continue; }
this[option] = options[option];
}
};
var indexOf = function(callbacks, callback) {
for (var i=0, l=callbacks.length; i<l; i++) {
if (callbacks[i][0] === callback) { return i; }
}
return -1;
};
var callbacksFor = function(object) {
var callbacks = object._promiseCallbacks;
if (!callbacks) {
callbacks = object._promiseCallbacks = {};
}
return callbacks;
};
var EventTarget = exports.EventTarget = {
mixin: function(object) {
object.on = this.on;
object.off = this.off;
object.trigger = this.trigger;
return object;
},
on: function(eventName, callback, binding) {
var allCallbacks = callbacksFor(this), callbacks;
binding = binding || this;
callbacks = allCallbacks[eventName];
if (!callbacks) {
callbacks = allCallbacks[eventName] = [];
}
if (indexOf(callbacks, callback) === -1) {
callbacks.push([callback, binding]);
}
},
off: function(eventName, callback) {
var allCallbacks = callbacksFor(this), callbacks;
if (!callback) {
allCallbacks[eventName] = [];
return;
}
callbacks = allCallbacks[eventName];
var index = indexOf(callbacks, callback);
if (index !== -1) { callbacks.splice(index, 1); }
},
trigger: function(eventName, options) {
var allCallbacks = callbacksFor(this),
callbacks, callbackTuple, callback, binding, event;
if (callbacks = allCallbacks[eventName]) {
for (var i=0, l=callbacks.length; i<l; i++) {
callbackTuple = callbacks[i];
callback = callbackTuple[0];
binding = callbackTuple[1];
if (typeof options !== 'object') {
options = { detail: options };
}
event = new Event(eventName, options);
callback.call(binding, event);
}
}
}
};
var Promise = exports.Promise = function(binding) {
this.binding = binding || this;
this.on('promise:resolved', function(event) {
this.trigger('success', { detail: event.detail });
}, this);
this.on('promise:failed', function(event) {
this.trigger('error', { detail: event.detail });
}, this);
};
var noop = function() {};
var invokeCallback = function(type, promise, callback, event) {
var value, error;
if (callback) {
try {
value = callback.call(promise.binding, event.detail);
} catch(e) {
error = e;
}
} else {
value = event.detail;
}
if (value instanceof Promise) {
value.then(function(value) {
promise.resolve( value );
}, function(error) {
promise.reject( error );
});
} else if (callback && value) {
promise.resolve(value);
} else if (error) {
promise.reject(error);
} else {
promise[type](value);
}
};
Promise.prototype = {
then: function(done, fail) {
var thenPromise = new Promise(this.binding);
this.on('promise:resolved', function(event) {
invokeCallback('resolve', thenPromise, done, event);
});
this.on('promise:failed', function(event) {
invokeCallback('reject', thenPromise, fail, event);
});
return thenPromise;
},
resolve: function(value) {
exports.async(function() {
this.trigger('promise:resolved', { detail: value });
this.isResolved = value;
}, this);
this.resolve = noop;
this.reject = noop;
},
reject: function(value) {
exports.async(function() {
this.trigger('promise:failed', { detail: value });
this.isRejected = value;
}, this);
this.resolve = noop;
this.reject = noop;
}
};
EventTarget.mixin(Promise.prototype);
})( global.RSVP = {} );
// *** UPNP SERVICE WRAPPER ***
var UPnP = function( serviceObj, opts ) {
this.options = opts || { debug: false };
this.svc = serviceObj;
// API stub
this.action = function() {};
if( !this.svc || !(this.svc instanceof NetworkService) ) {
this.debugLog('First argument provided in constructor MUST be a valid NetworkService object');
return this;
}
if( !this.svc.type || this.svc.type.indexOf('upnp:') !== 0 ) {
this.debugLog('First argument provided in constructor MUST be a _UPnP_ NetworkService object');
return this;
}
this.svcType = this.svc.type.replace('upnp:', '');
this.svcUrl = this.svc.url;
var self = this;
// Full API
this.action = function( upnpAction, upnpParameters, callback ) {
var promise = new RSVP.Promise( this );
// Handle .action( name, callback )
if( !callback && Object.prototype.toString.call( upnpParameters ) == '[object Function]' ) {
callback = upnpParameters;
upnpParameters = {};
}
// Generate a callback stub if a Function has not been provided
if( !callback || Object.prototype.toString.call( callback ) !== '[object Function]' ) {
callback = function(e, response) {};
}
// Create a UPnP XML Request message
var svcMsg = this.createRequest( this.svcType, upnpAction, upnpParameters );
// Send the UPnP XML Request message to the current service
this.sendRequest( this.svcType, upnpAction, this.svcUrl, svcMsg, function(e, xmlResponse) {
if(e !== null) {
callback.apply(self, [ e, null ]);
promise.reject( e );
return;
}
// Parse UPnP XML Response message
self.handleResponse( upnpAction, xmlResponse.responseXML || "", upnpParameters, function(e, upnpResponse) {
// Fire callback
callback.apply(self, [e, upnpResponse]);
// Fire promise
if( e !== null) {
promise.reject( e );
} else {
promise.resolve( upnpResponse );
}
});
});
return promise;
}
};
UPnP.prototype.constructor = UPnP;
// UPnP XML MESSAGING TEMPLATES
UPnP.prototype.requestTemplate = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
"<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" " +
"xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
"\t<s:Body>\n" +
"\t\t<u:{ACTION_NAME} xmlns:u=\"{ACTION_TYPE}\">\n" +
"{ACTION_VARS}" +
"\t\t</u:{ACTION_NAME}>\n" +
"\t</s:Body>\n" +
"</s:Envelope>\n";
// UPNP PRIMITIVE VARIABLE TYPE CHECKERS
// from http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf
UPnP.prototype.types = {
"i1": function(val, allowedValues, toNative) {
var i8 = new Int8Array(1);
i8[0] = val;
return UPnP.prototype.checkAllowedValues( i8[0] , allowedValues );
},
"i2": function(val, allowedValues, toNative) {
var i16 = new Int16Array(1);
i16[0] = val;
return UPnP.prototype.checkAllowedValues( i16[0] , allowedValues );
},
"i4": function(val, allowedValues, toNative) {
var i32 = new Int32Array(1);
i32[0] = val;
return UPnP.prototype.checkAllowedValues( i32[0] , allowedValues );
},
"ui1": function(val, allowedValues, toNative) {
var ui8 = new Uint8Array(1);
ui8[0] = val;
return UPnP.prototype.checkAllowedValues( ui8[0] , allowedValues );
},
"ui2": function(val, allowedValues, toNative) {
var ui16 = new Uint16Array(1);
ui16[0] = val;
return UPnP.prototype.checkAllowedValues( ui16[0] , allowedValues );
},
"ui4": function(val, allowedValues, toNative) {
var ui32 = new Uint32Array(1);
ui32[0] = val;
return UPnP.prototype.checkAllowedValues( ui32[0] , allowedValues );
},
"int": function(val, allowedValues, toNative) {
if(val === undefined || val === null || isNaN(val)) {
val = 0;
} else if (typeof val !== 'number') {
if(val === true) {
val = 1;
} else {
val = parseInt(val + "", 10);
}
}
if( !toNative ) {
val = expandExponential( val + "" );
}
if(isNaN(val)) val = 0;
return UPnP.prototype.checkAllowedValues( val , allowedValues );
},
"r4": function(val, allowedValues, toNative) {
var f32 = new Float32Array(1);
f32[0] = val;
if( !toNative ) {
return f32[0] ? expandExponential( f32[0] + "" ) : "0";
}
return UPnP.prototype.checkAllowedValues( f32[0] , allowedValues );
},
"r8": function(val, allowedValues, toNative) {
var f64 = new Float64Array(1);
f64[0] = val;
if( !toNative ) {
return f64[0] ? expandExponential( f64[0] + "" ) : "0";
}
return UPnP.prototype.checkAllowedValues( f64[0] , allowedValues );
},
"number": function(val, allowedValues, toNative) {
return UPnP.prototype.types['r8'](val, allowedValues, toNative);
},
"fixed_14_4": function(val, allowedValues, toNative) {
return UPnP.prototype.types['float'](val, allowedValues, toNative);
},
"float": function(val, allowedValues, toNative) {
var _float = parseFloat(val);
if( !toNative ) {
return _float ? expandExponential( _float + "" ) : "0";
}
return UPnP.prototype.checkAllowedValues( _float , allowedValues );
},
"char": function(val, allowedValues, toNative) {
if(val === undefined || val === null || val === "" || (val + "") == "NaN" || val instanceof RegExp) {
val = "";
} else if(val === false || val === true || val == "true" || val == "yes" || val == "false" || val == "no") {
val = (val === true || val == "true" || val == "yes") ? "1" : "0";
} else {
val = val.toString();
}
if(val.length > 0) {
val = UPnP.prototype.checkAllowedValues( val, allowedValues );
return val.charAt(0);
}
return val;
},
"string": function(val, allowedValues, toNative) {
if(val === undefined || val === null || val === "" || (val + "") == "NaN" || val instanceof RegExp) {
val = "";
}
val = val + "";
val = UPnP.prototype.checkAllowedValues( val, allowedValues );
return val;
},
"date": function(val, allowedValues, toNative) {
return UPnP.prototype.types['dateTime'](val, allowedValues, toNative);
},
"dateTime": function(val, allowedValues, toNative) {
if( toNative ) { // Parse to ECMA Date object
if( Object.prototype.toString.call( val ) == '[object String]' ) {
val = new Date( parseDate( val ) );
if(isNaN(val)) val = "";
} else if( Object.prototype.toString.call( val ) == '[object Date]' ) {
val = val;
}
} else { // Parse to ISO-8601 string
if( Object.prototype.toString.call( val ) == '[object String]' ) {
val = val + "";
} else if( Object.prototype.toString.call( val ) == '[object Date]' ) {
if(val.toString() == "Invalid Date") { // opera specific functionality
val = "";
} else {
val = val.toISOString();
}
}
}
val = UPnP.prototype.checkAllowedValues( val, allowedValues );
return val;
},
"dateTime_tz": function(val, allowedValues, toNative) {
return UPnP.prototype.types['dateTime'](val, allowedValues, toNative);
},
"time": function(val, allowedValues, toNative) {
return UPnP.prototype.types['dateTime'](val, allowedValues, toNative);
},
"time_tz": function(val, allowedValues, toNative) {
return UPnP.prototype.types['dateTime'](val, allowedValues, toNative);
},
"boolean": function(val, allowedValues, toNative) {
if(val == 'false' || val == 'f' || val == 'no' || val == 'n' || val instanceof RegExp || val <= 0 || !val) {
if(toNative) {
val = false;
} else {
val = "0";
}
} else {
if(toNative) {
val = true;
} else {
val = "1";
}
}
return val;
},
"bin_base64": function(val, allowedValues, toNative) {
if(val === undefined || val === null || val === "" || (val + "") == "NaN" || val instanceof RegExp) {
val = "";
}
if(val === true || val === false ) {
val = val === true ? "1" : "0";
}
if( toNative ) { // convert to string
val = atob( val + "" );
} else { // convert to base64
val = btoa( val + "" );
}
return UPnP.prototype.checkAllowedValues( val, allowedValues );
},
"bin_hex": function(val, allowedValues, toNative) {
if(val === undefined || val === null || val === "" || (val + "") == "NaN" || val instanceof RegExp) {
val = "";
}
if(val === true || val === false ) {
val = val === true ? "1" : "0";
}
if( toNative ) { // convert to string
val = htoa( val + "" );
} else { // convert to hex
val = atoh( val + "" );
}
return UPnP.prototype.checkAllowedValues( val, allowedValues );
},
"uri": function(val, allowedValues, toNative) {
if( toNative ) {
val = decodeURI(val + "");
}
return UPnP.prototype.types['string'](val, allowedValues); // No URI syntax checking
},
"uuid": function(val, allowedValues, toNative) {
return UPnP.prototype.types['string'](val, allowedValues); // No UUID syntax checking
}
};
// Override .types toString function
for(var type in UPnP.prototype.types) {
UPnP.prototype.types[ type ]._name = type;
UPnP.prototype.types[ type ]["toString"] = function() { return this._name; };
}
UPnP.prototype.createRequest = function( upnpServiceType, upnpActionName, parameters ) {
parameters = this.formatParameters( parameters );
this.debugLog( 'Creating UPnP XML Control Message for [' + upnpActionName + '] with parameters:' );
this.debugLog( JSON ? JSON.stringify( parameters ) : null );
// Build basic part of XML request
var svcMsg = this.requestTemplate.replace(/\{ACTION_TYPE\}/g, encodeXML(trim(upnpServiceType)));
svcMsg = svcMsg.replace(/\{ACTION_NAME\}/g, encodeXML(trim(upnpActionName)));
var svcInParams = parameters['request'],
svcInParams_xml = "";
if( svcInParams ) {
// Generate XML parameters syntax
for(var svcParam in svcInParams) {
// Perform type checking sanitization
if( svcInParams[ svcParam ]['type'] !== undefined && svcInParams[ svcParam ]['type'] !== null ) {
// get the primitive sanitization function
var typeCheck = this.types[ svcInParams[ svcParam ]['type'] ];
if( typeCheck ) {
svcInParams[ svcParam ]['value'] = typeCheck( svcInParams[ svcParam ]['value'], svcInParams[ svcParam ][ 'allowedValueList' ], false );
}
}
if( svcInParams[ svcParam ]['value'] === null || svcInParams[ svcParam ]['value'] === undefined ) {
svcInParams[ svcParam ]['value'] = ""; // pass through empty value
}
svcInParams_xml += "\t\t\t<" + svcParam + ">" + encodeXML(trim(svcInParams[ svcParam ]['value'] + "")) + "</" + svcParam + ">\n";
}
}
// Add ACTION_VARS, if any
svcMsg = svcMsg.replace(/\{ACTION_VARS\}/g, svcInParams_xml);
// DEBUG
this.debugLog( '[REQUEST] (SOAPAction: ' + upnpServiceType + '#' + upnpActionName + ') ' + svcMsg );
return svcMsg;
};
UPnP.prototype.sendRequest = function( upnpServiceType, upnpActionName, upnpEndpointURL, upnpRequestXML, onUPnPResponse ) {
// Send XML action message
try {
var xhr = xhrManager.get();
xhr.open( 'POST', upnpEndpointURL );
xhr.setRequestHeader('SOAPAction', '"' + upnpServiceType.replace(/[\"\#]/g, '') + '#' + upnpActionName.replace(/[\"\#]/g, '') + '"');
xhr.setRequestHeader('Content-Type', 'text/xml; charset="utf-8";');
xhr.onabort = xhr.onerror = function () {
onUPnPResponse.apply(undefined, [ new UPnPError('XmlHttpRequest failed'), null ]);
xhrManager.release( xhr );
};
var self = this;
xhr.onreadystatechange = function () {
if ( this.readyState < 4 ) return;
if( this.status !== 200 ) {
onUPnPResponse.apply(undefined, [ new UPnPError('XmlHttpRequest expected 200 OK. Received ' + this.status + ' response.'), null ]);
self.debugLog( "[ERROR-RESPONSE] " + this.responseText );
xhrManager.release( xhr );
return;
}
self.debugLog( "[SUCCESS-RESPONSE] " + this.responseText );
onUPnPResponse.apply(this, [ null, this ]);
xhrManager.release( xhr );
};
xhr.send( upnpRequestXML );
} catch(e) {
onUPnPResponse.apply(undefined, [ new UPnPError( e ), null ]);
}
return this; // allow method chaining
};
UPnP.prototype.handleResponse = function( upnpActionName, upnpResponseXML, parameters, onUPnPResolved ) {
parameters = this.formatParameters( parameters );
var svcOutParams = parameters['response'];
var responseJSON = {};
// Process all XML response variables
var responseContainer = upnpResponseXML.getElementsByTagName ?
upnpResponseXML.getElementsByTagName( upnpActionName + "Response" ) : null;
// If we don't have any response variables, try again with the namespace attached (required for lib to work in Firefox)
if( responseContainer === null || responseContainer.length <= 0 ) {
responseContainer = upnpResponseXML.getElementsByTagName ?
upnpResponseXML.getElementsByTagName( "u:" + upnpActionName + "Response" ) : null;
}
if( responseContainer !== null && responseContainer.length > 0 ) {
var responseVars = responseContainer[0].childNodes;
if( responseVars && responseVars.length > 0 ) {
for(var i = 0, l = responseVars.length; i < l; i++ ) {
var varName = responseVars[i].nodeName;
var varVal = responseVars[i].textContent? trim(decodeXML(responseVars[i].textContent + "")) : null;
// Perform type checking sanitization if requested
if( svcOutParams[ varName ] && svcOutParams[ varName ]['type'] !== undefined && svcOutParams[ varName ]['type'] !== null ) {
// get the primitive sanitization function
var typeCheck = this.types[ svcOutParams[ varName ]['type'] ];
if( typeCheck ) {
varVal = typeCheck( varVal, svcOutParams[ varName ][ 'allowedValueList' ] || null, true );
}
}
responseJSON[ varName ] = (varVal !== undefined && varVal !== null) ? varVal : "";
}
}
}
// Fire callback with the parsed action response data
onUPnPResolved.apply(undefined, [ null, { data: responseJSON } ]);
return this; // allow method chaining
};
UPnP.prototype.formatParameter = function( parameter ) {
var param = {};
if( parameter['type'] !== undefined || parameter['value'] !== undefined || parameter['allowedValueList'] !== undefined ) {
param = parameter;
if( parameter['allowedValueList'] !== undefined && typeof parameter['allowedValueList'] !== 'array' ) {
parameter['allowedValueList'] = [];
}
} else {
param = { value: parameter };
}
// check type is valid, otherwise discard requested type
if( param['type'] !== undefined && param['type'] !== null ) {
if( !this.types[ (param['type'] + "") ] ) {
delete param['type'];
}
}
return param;
};
// Normalize the input parameters argument
UPnP.prototype.formatParameters = function( parameters ) {
if(parameters === undefined || parameters === null) {
parameters = {};
}
if(!parameters['request']) {
parameters['request'] = {};
}
if(!parameters['response']) {
parameters['response'] = {};
}
for( var param in parameters ) {
if( param === 'request' || param === 'response' ) {
for(var processParam in parameters[param] ) {
parameters[param][ processParam ] = this.formatParameter( parameters[param][ processParam ] );
}
} else { // append as 'request' parameter and remove from root object
parameters['request'][ param ] = this.formatParameter( parameters[ param ] );
delete parameters[ param ];
}
}
return parameters;
};
// ALLOWED VALUE LIST CHECKER
UPnP.prototype.checkAllowedValues = function( val, allowedValueList ) {
// Check against allowedValues if provided
if( val && allowedValueList && Object.prototype.toString.call( allowedValueList ) == '[object Array]' ) {
var matchFound = false;
for( var i = 0, l = allowedValueList.length; i < l; i++ ) {
if( val == allowedValueList[i] ) {
matchFound = true;
break;
}
}
if( !matchFound ) {
return "";
}
}
return val;
};
// UPnP DEBUGGER
UPnP.prototype.debugLog = function( msg, level ) {
if( (this.options && this.options.debug) ) {
console[level || 'log']( "Plug.UPnP: " + msg );
}
};
// UPnP HTTP MESSAGING MANAGER
var xhrManager = ( function() {
var pool = [];
return {
get: function () {
return pool.pop() || new XMLHttpRequest({mozSystem: true});
},
release: function( xhr ) {
xhr.onreadystatechange = function () {};
pool.push( xhr );
}
};
}() );
// UPnP ERROR OBJECT
var UPnPError = function( description ) {
return {
'description': description
};
};
// *** CUSTOM TYPE FUNCTIONS ***
// BASE64 FUNCTIONS
// (forked from https://github.com/dankogai/js-base64/blob/master/base64.js)
var b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var b64tab = (function(bin) {
var t = {};
for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
return t;
})(b64chars);
var cb_encode = function(ccc) {
var padlen = [0, 2, 1][ccc.length % 3],
ord = ccc.charCodeAt(0) << 16
| ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
| ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
chars = [
b64chars.charAt( ord >>> 18),
b64chars.charAt((ord >>> 12) & 63),
padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
];
return chars.join('');
};
var cb_decode = function(cccc) {
var len = cccc.length,
padlen = len % 4,
n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
| (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
| (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0)
| (len > 3 ? b64tab[cccc.charAt(3)] : 0),
chars = [
String.fromCharCode( n >>> 16),
String.fromCharCode((n >>> 8) & 0xff),
String.fromCharCode( n & 0xff)
];
chars.length -= [0, 0, 2, 1][padlen];
return chars.join('');
};
var btoa = global.btoa || function(b) {
return b.replace(/[\s\S]{1,3}/g, cb_encode);
};
var atob = global.atob || function(a){
return a.replace(/[\s\S]{1,4}/g, cb_decode);
};
// HEX FUNCTIONS
var hexChars = '0123456789abcdef';
var hextab = (function(bin) {
var t = {};
for (var i = 0, l = bin.length; i < l; i++) t[i] = bin.charAt(i);
return t;
})(hexChars);
function toHex(n){
var result = ''
var start = true;
for (var i=32; i>0;){
i-=4;
var digit = (n>>i) & 0xf;
if (!start || digit != 0){
start = false;
result += hextab[digit];
}
}
return (result==''?'0':result);
}
function pad(str, len, pad){
var result = str;
for (var i=str.length; i<len; i++){
result = pad + result;
}
return result;
}
function ntos(n){
n=n.toString(16);
if (n.length == 1) n="0"+n;
n="%"+n;
return unescape(n);
}
function htoa(str){
str = str.toUpperCase().replace(new RegExp("s/[^0-9A-Z]//g"));
var result = "";
var nextchar = "";
for (var i=0; i<str.length; i++){
nextchar += str.charAt(i);
if (nextchar.length == 2){
result += ntos( parseInt(nextchar, 16) );
nextchar = "";
}
}
return result;
}
function atoh(str){
var result = "";
for (var i=0; i<str.length; i++){
result += pad(toHex(str.charCodeAt(i)&0xff),2,'0');
}
return result;
}
// NUMBER FUNCTIONS
function expandExponential( str ){
return str.replace(/^([+-])?(\d+).?(\d*)[eE]([-+]?\d+)$/, function(x, s, n, f, c){
var l = +c < 0, i = n.length + +c, x = (l ? n : f).length,
c = ((c = Math.abs(c)) >= x ? c - x + l : 0),
z = (new Array(c + 1)).join("0"), r = n + f;
return (s || "") + (l ? r = z + r : r += z).substr(0, i += l ? z.length : 0) + (i < r.length ? "." + r.substr(i) : "");
});
};
// STRING FUNCTIONS
var trim = function( str ) {
if(String.prototype.trim) {
return str.trim();
} else {
return str.replace(/^\s+|\s+$/g, "");
}
};
var encodeXML = function ( str ) {
return str.replace(/\&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/'/g, '&#39;')
.replace(/"/g, '&quot;');
};
var decodeXML = function ( str ) {
return str.replace(/\&quot;/g, '"')
.replace(/\&\#39;/g, '\'')
.replace(/\&gt;/g, '>')
.replace(/\&lt;/g, '<')
.replace(/\&amp;/g, '&');
};
/**
* Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601>
* © 2011 Colin Snover <http://zetafleet.com>
* Released under MIT license.
*
* 2012 - Modifications to accept times without dates - by Rich Tibbett
*/
var parseDate = function (date) {
var timestamp, struct, minutesOffset = 0, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
if(typeof date !== 'string') date += "";
if ((struct = /^((\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:[T\s]?(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{4}))?)?$/.exec(date))) {
struct = struct.slice(1, struct.length);
// avoid NaN timestamps caused by “undefined” values being passed to Date.UTC
for (var i = 0, k; (k = numericKeys[i]); ++i) {
struct[k] = +struct[k] || 0;
}
// allow undefined days and months
struct[2] = (+struct[2] || 1) - 1;
struct[3] = +struct[3] || 1;
if (struct[8] !== 'Z' && struct[9] !== undefined) {
minutesOffset = (( struct[10] / 100 ) - (( struct[10] / 100 ) % 1)) * 60 + (struct[10] % 100);
if (struct[9] === '+') {
minutesOffset = 0 - minutesOffset;
}
}
timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]);
}
else {
timestamp = Date.parse ? Date.parse(date) : NaN;
}
return timestamp;
};
// *** GLOBAL ASSIGNMENT ***
if( !global.Plug ) {
global.Plug = {};
}
if( !global.Plug.UPnP ) {
global.Plug.UPnP = UPnP;
}
})( this );