// Options that can be set on the host page: //window.openid_visible_iframe = true; // causes the hidden iframe to show up //window.openid_trace = true; // causes lots of messages function trace(msg) { if (window.openid_trace) { if (!window.tracediv) { window.tracediv = document.createElement("ol"); document.body.appendChild(window.tracediv); } var el = document.createElement("li"); el.appendChild(document.createTextNode(msg)); window.tracediv.appendChild(el); //alert(msg); } } /// Removes a given element from the array. /// True if the element was in the array, or false if it was not found. Array.prototype.remove = function(element) { function elementToRemoveLast(a, b) { if (a == element) { return 1; } if (b == element) { return -1; } return 0; } this.sort(elementToRemoveLast); if (this[this.length - 1] == element) { this.pop(); return true; } else { return false; } }; function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url, success_icon_url, failure_icon_url, throttle, timeout, assertionReceivedCode, loginButtonText, loginButtonToolTip, retryButtonText, retryButtonToolTip, busyToolTip, identifierRequiredMessage, loginInProgressMessage, authenticatedByToolTip, authenticatedAsToolTip, authenticationFailedToolTip, discoverCallback, discoveryFailedCallback) { box.dnoi_internal = new Object(); if (assertionReceivedCode) { box.dnoi_internal.onauthenticated = function(sender, e) { eval(assertionReceivedCode); } } box.dnoi_internal.originalBackground = box.style.background; box.timeout = timeout; box.dnoi_internal.discoverIdentifier = discoverCallback; box.dnoi_internal.authenticationRequests = new Array(); // The possible authentication results var authSuccess = new Object(); var authRefused = new Object(); var timedOut = new Object(); function FrameManager(maxFrames) { this.queuedWork = new Array(); this.frames = new Array(); this.maxFrames = maxFrames; /// Called to queue up some work that will use an iframe as soon as it is available. /// /// A delegate that must return the url to point to iframe to. /// Its first parameter is the iframe created to service the request. /// It will only be called when the work actually begins. /// this.enqueueWork = function(job) { // Assign an iframe to this task immediately if there is one available. if (this.frames.length < this.maxFrames) { this.createIFrame(job); } else { this.queuedWork.unshift(job); } }; /// Clears the job queue and immediately closes all iframes. this.cancelAllWork = function() { trace('Canceling all open and pending iframes.'); while (this.queuedWork.pop()); this.closeFrames(); }; /// An event fired when a frame is closing. this.onJobCompleted = function() { // If there is a job in the queue, go ahead and start it up. if (job = this.queuedWork.pop()) { this.createIFrame(job); } } this.createIFrame = function(job) { var iframe = document.createElement("iframe"); if (!window.openid_visible_iframe) { iframe.setAttribute("width", 0); iframe.setAttribute("height", 0); iframe.setAttribute("style", "display: none"); } iframe.setAttribute("src", job(iframe)); iframe.openidBox = box; box.parentNode.insertBefore(iframe, box); this.frames.push(iframe); return iframe; }; this.closeFrames = function() { if (this.frames.length == 0) { return false; } for (var i = 0; i < this.frames.length; i++) { if (this.frames[i].parentNode) { this.frames[i].parentNode.removeChild(this.frames[i]); } } while (this.frames.length > 0) { this.frames.pop(); } return true; }; this.closeFrame = function(frame) { if (frame.parentNode) { frame.parentNode.removeChild(frame); } var removed = this.frames.remove(frame); this.onJobCompleted(); return removed; }; } box.dnoi_internal.authenticationIFrames = new FrameManager(throttle); box.dnoi_internal.constructButton = function(text, tooltip, onclick) { var button = document.createElement('button'); button.textContent = text; // Mozilla button.value = text; // IE button.title = tooltip != null ? tooltip : ''; button.onclick = onclick; button.style.visibility = 'hidden'; button.style.position = 'absolute'; button.style.padding = "0px"; button.style.fontSize = '8px'; button.style.top = "1px"; button.style.bottom = "1px"; button.style.right = "2px"; box.parentNode.appendChild(button); return button; } box.dnoi_internal.constructIcon = function(imageUrl, tooltip, rightSide, visible, height) { var icon = document.createElement('img'); icon.src = imageUrl; icon.title = tooltip != null ? tooltip : ''; icon.originalTitle = icon.title; if (!visible) { icon.style.visibility = 'hidden'; } icon.style.position = 'absolute'; icon.style.top = "2px"; icon.style.bottom = "2px"; // for FireFox (and IE7, I think) if (height) { icon.style.height = height; // for Chrome and IE8 } if (rightSide) { icon.style.right = "2px"; } else { icon.style.left = "2px"; } box.parentNode.appendChild(icon); return icon; } box.dnoi_internal.prefetchImage = function(imageUrl) { var img = document.createElement('img'); img.src = imageUrl; img.style.display = 'none'; box.parentNode.appendChild(img); return img; } function findParentForm(element) { if (element == null || element.nodeName == "FORM") { return element; } return findParentForm(element.parentNode); }; box.parentForm = findParentForm(box); function findOrCreateHiddenField() { var name = box.name + '_openidAuthData'; var existing = window.document.getElementsByName(name); if (existing && existing.length > 0) { return existing[0]; } var hiddenField = document.createElement('input'); hiddenField.setAttribute("name", name); hiddenField.setAttribute("type", "hidden"); box.parentForm.appendChild(hiddenField); return hiddenField; }; box.dnoi_internal.loginButton = box.dnoi_internal.constructButton(loginButtonText, loginButtonToolTip, function() { var discoveryInfo = box.dnoi_internal.authenticationRequests[box.lastDiscoveredIdentifier]; if (discoveryInfo == null) { trace('Ooops! Somehow the login button click event was invoked, but no openid discovery information for ' + box.lastDiscoveredIdentifier + ' is available.'); return; } // The login button always sends a setup message to the first OP. var selectedProvider = discoveryInfo[0]; selectedProvider.trySetup(); return false; }); box.dnoi_internal.retryButton = box.dnoi_internal.constructButton(retryButtonText, retryButtonToolTip, function() { box.timeout += 5000; // give the retry attempt 5s longer than the last attempt box.dnoi_internal.performDiscovery(box.value); return false; }); box.dnoi_internal.openid_logo = box.dnoi_internal.constructIcon(openid_logo_url, null, false, true); box.dnoi_internal.op_logo = box.dnoi_internal.constructIcon('', authenticatedByToolTip, false, false, "16px"); box.dnoi_internal.spinner = box.dnoi_internal.constructIcon(spinner_url, busyToolTip, true); box.dnoi_internal.success_icon = box.dnoi_internal.constructIcon(success_icon_url, authenticatedAsToolTip, true); //box.dnoi_internal.failure_icon = box.dnoi_internal.constructIcon(failure_icon_url, authenticationFailedToolTip, true); // Disable the display of the DotNetOpenId logo //box.dnoi_internal.dnoi_logo = box.dnoi_internal.constructIcon(dotnetopenid_logo_url); box.dnoi_internal.dnoi_logo = box.dnoi_internal.openid_logo; box.dnoi_internal.setVisualCue = function(state, authenticatedBy, authenticatedAs) { box.dnoi_internal.openid_logo.style.visibility = 'hidden'; box.dnoi_internal.dnoi_logo.style.visibility = 'hidden'; box.dnoi_internal.op_logo.style.visibility = 'hidden'; box.dnoi_internal.openid_logo.title = box.dnoi_internal.openid_logo.originalTitle; box.dnoi_internal.spinner.style.visibility = 'hidden'; box.dnoi_internal.success_icon.style.visibility = 'hidden'; // box.dnoi_internal.failure_icon.style.visibility = 'hidden'; box.dnoi_internal.loginButton.style.visibility = 'hidden'; box.dnoi_internal.retryButton.style.visibility = 'hidden'; box.title = ''; box.dnoi_internal.state = state; if (state == "discovering") { box.dnoi_internal.dnoi_logo.style.visibility = 'visible'; box.dnoi_internal.spinner.style.visibility = 'visible'; box.dnoi_internal.claimedIdentifier = null; box.title = ''; window.status = "Discovering OpenID Identifier '" + box.value + "'..."; } else if (state == "authenticated") { var opLogo = box.dnoi_internal.deriveOPFavIcon(); if (opLogo) { box.dnoi_internal.op_logo.src = opLogo; box.dnoi_internal.op_logo.style.visibility = 'visible'; box.dnoi_internal.op_logo.title = box.dnoi_internal.op_logo.originalTitle.replace('{0}', authenticatedBy.getHost()); } else { box.dnoi_internal.openid_logo.style.visibility = 'visible'; box.dnoi_internal.openid_logo.title = box.dnoi_internal.op_logo.originalTitle.replace('{0}', authenticatedBy.getHost()); } box.dnoi_internal.success_icon.style.visibility = 'visible'; box.dnoi_internal.success_icon.title = box.dnoi_internal.success_icon.originalTitle.replace('{0}', authenticatedAs); box.title = box.dnoi_internal.claimedIdentifier; window.status = "Authenticated as " + box.value; } else if (state == "setup") { var opLogo = box.dnoi_internal.deriveOPFavIcon(); if (opLogo) { box.dnoi_internal.op_logo.src = opLogo; box.dnoi_internal.op_logo.style.visibility = 'visible'; } else { box.dnoi_internal.openid_logo.style.visibility = 'visible'; } box.dnoi_internal.loginButton.style.visibility = 'visible'; box.dnoi_internal.claimedIdentifier = null; window.status = "Authentication requires setup."; } else if (state == "failed") { box.dnoi_internal.openid_logo.style.visibility = 'visible'; //box.dnoi_internal.failure_icon.style.visibility = 'visible'; box.dnoi_internal.retryButton.style.visibility = 'visible'; box.dnoi_internal.claimedIdentifier = null; window.status = authenticationFailedToolTip; box.title = authenticationFailedToolTip; } else if (state = '' || state == null) { box.dnoi_internal.openid_logo.style.visibility = 'visible'; box.title = ''; box.dnoi_internal.claimedIdentifier = null; window.status = null; } else { box.dnoi_internal.claimedIdentifier = null; trace('unrecognized state ' + state); } } box.dnoi_internal.isBusy = function() { return box.dnoi_internal.state == 'discovering' || box.dnoi_internal.authenticationRequests[box.lastDiscoveredIdentifier].busy(); }; box.dnoi_internal.canAttemptLogin = function() { if (box.value.length == 0) return false; if (box.dnoi_internal.authenticationRequests[box.value] == null) return false; if (box.dnoi_internal.state == 'failed') return false; return true; }; box.dnoi_internal.getUserSuppliedIdentifierResults = function() { return box.dnoi_internal.authenticationRequests[box.value]; } box.dnoi_internal.isAuthenticated = function() { var results = box.dnoi_internal.getUserSuppliedIdentifierResults(); return results != null && results.findSuccessfulRequest() != null; } box.dnoi_internal.onSubmit = function() { var hiddenField = findOrCreateHiddenField(); if (box.dnoi_internal.isAuthenticated()) { // stick the result in a hidden field so the RP can verify it hiddenField.setAttribute("value", box.dnoi_internal.authenticationRequests[box.value].successAuthData); } else { hiddenField.setAttribute("value", ''); if (box.dnoi_internal.isBusy()) { alert(loginInProgressMessage); } else { if (box.value.length > 0) { // submitPending will be true if we've already tried deferring submit for a login, // in which case we just want to display a box to the user. if (box.dnoi_internal.submitPending || !box.dnoi_internal.canAttemptLogin()) { alert(identifierRequiredMessage); } else { // The user hasn't clicked "Login" yet. We'll click login for him, // after leaving a note for ourselves to automatically click submit // when login is complete. box.dnoi_internal.submitPending = box.dnoi_internal.submitButtonJustClicked; if (box.dnoi_internal.submitPending == null) { box.dnoi_internal.submitPending = true; } box.dnoi_internal.loginButton.onclick(); return false; // abort submit for now } } else { return true; } } return false; } return true; }; /// /// Records which submit button caused this openid box to question whether it /// was ready to submit the user's identifier so that that button can be re-invoked /// automatically after authentication completes. /// box.dnoi_internal.setLastSubmitButtonClicked = function(evt) { var button; if (evt.target) { button = evt.target; } else { button = evt.srcElement; } box.dnoi_internal.submitButtonJustClicked = button; }; // Find all submit buttons and hook their click events so that we can validate // whether we are ready for the user to postback. var inputs = document.getElementsByTagName('input'); for (var i = 0; i < inputs.length; i++) { var el = inputs[i]; if (el.type == 'submit') { if (el.attachEvent) { el.attachEvent("onclick", box.dnoi_internal.setLastSubmitButtonClicked); } else { el.addEventListener("click", box.dnoi_internal.setLastSubmitButtonClicked, true); } } } /// /// Returns the URL of the authenticating OP's logo so it can be displayed to the user. /// box.dnoi_internal.deriveOPFavIcon = function() { var response = box.dnoi_internal.getUserSuppliedIdentifierResults().successAuthData; if (!response || response.length == 0) return; var authResult = new Uri(response); var opUri; if (authResult.getQueryArgValue("openid.op_endpoint")) { opUri = new Uri(authResult.getQueryArgValue("openid.op_endpoint")); } if (authResult.getQueryArgValue("dotnetopenid.op_endpoint")) { opUri = new Uri(authResult.getQueryArgValue("dotnetopenid.op_endpoint")); } else if (authResult.getQueryArgValue("openid.user_setup_url")) { opUri = new Uri(authResult.getQueryArgValue("openid.user_setup_url")); } else return null; var favicon = opUri.getAuthority() + "/favicon.ico"; return favicon; }; box.dnoi_internal.createDiscoveryInfo = function(discoveryInfo, identifier) { this.identifier = identifier; // The claimed identifier may be null if the user provided an OP Identifier. this.claimedIdentifier = discoveryInfo.claimedIdentifier; trace('Discovered claimed identifier: ' + this.claimedIdentifier); // Add extra tracking bits and behaviors. this.findByEndpoint = function(opEndpoint) { for (var i = 0; i < this.length; i++) { if (this[i].endpoint == opEndpoint) { return this[i]; } } }; this.findSuccessfulRequest = function() { for (var i = 0; i < this.length; i++) { if (this[i].result == authSuccess) { return this[i]; } } }; this.busy = function() { for (var i = 0; i < this.length; i++) { if (this[i].busy()) { return true; } } }; this.abortAll = function() { // Abort all other asynchronous authentication attempts that may be in progress. box.dnoi_internal.authenticationIFrames.cancelAllWork(); for (var i = 0; i < this.length; i++) { this[i].abort(); } }; this.tryImmediate = function() { if (this.length > 0) { for (var i = 0; i < this.length; i++) { box.dnoi_internal.authenticationIFrames.enqueueWork(this[i].tryImmediate); } } else { box.dnoi_internal.discoveryFailed(null, this.identifier); } }; this.length = discoveryInfo.requests.length; for (var i = 0; i < discoveryInfo.requests.length; i++) { this[i] = new box.dnoi_internal.createTrackingRequest(discoveryInfo.requests[i], identifier); } }; box.dnoi_internal.createTrackingRequest = function(requestInfo, identifier) { // It's possible during a postback that discovered request URLs are not available. this.immediate = requestInfo.immediate ? new Uri(requestInfo.immediate) : null; this.setup = requestInfo.setup ? new Uri(requestInfo.setup) : null; this.endpoint = new Uri(requestInfo.endpoint); this.identifier = identifier; var self = this; // closure so that delegates have the right instance this.host = self.endpoint.getHost(); this.getDiscoveryInfo = function() { return box.dnoi_internal.authenticationRequests[self.identifier]; } this.busy = function() { return self.iframe != null || self.popup != null; }; this.completeAttempt = function() { if (!self.busy()) return false; if (self.iframe) { trace('iframe hosting ' + self.endpoint + ' now CLOSING.'); box.dnoi_internal.authenticationIFrames.closeFrame(self.iframe); self.iframe = null; } if (self.popup) { self.popup.close(); self.popup = null; } if (self.timeout) { window.clearTimeout(self.timeout); self.timeout = null; } if (!self.getDiscoveryInfo().busy() && self.getDiscoveryInfo().findSuccessfulRequest() == null) { trace('No asynchronous authentication attempt is in progress. Display setup view.'); // visual cue that auth failed box.dnoi_internal.setVisualCue('setup'); } return true; }; this.authenticationTimedOut = function() { if (self.completeAttempt()) { trace(self.host + " timed out"); self.result = timedOut; } }; this.authSuccess = function(authUri) { if (self.completeAttempt()) { trace(self.host + " authenticated!"); self.result = authSuccess; self.response = authUri; box.dnoi_internal.authenticationRequests[self.identifier].abortAll(); } }; this.authFailed = function() { if (self.completeAttempt()) { //trace(self.host + " failed authentication"); self.result = authRefused; } }; this.abort = function() { if (self.completeAttempt()) { trace(self.host + " aborted"); // leave the result as whatever it was before. } }; this.tryImmediate = function(iframe) { self.abort(); // ensure no concurrent attempts self.timeout = setTimeout(function() { self.authenticationTimedOut(); }, box.timeout); trace('iframe hosting ' + self.endpoint + ' now OPENING.'); self.iframe = iframe; //trace('initiating auth attempt with: ' + self.immediate); return self.immediate; }; this.trySetup = function() { self.abort(); // ensure no concurrent attempts window.waiting_openidBox = box; self.popup = window.open(self.setup, 'opLogin', 'status=0,toolbar=0,location=1,resizable=1,scrollbars=1,width=800,height=600'); }; }; /***************************************** * Flow *****************************************/ /// Called to initiate discovery on some identifier. box.dnoi_internal.performDiscovery = function(identifier) { box.dnoi_internal.authenticationIFrames.closeFrames(); box.dnoi_internal.setVisualCue('discovering'); box.lastDiscoveredIdentifier = identifier; box.dnoi_internal.discoverIdentifier(identifier, box.dnoi_internal.discoveryResult, box.dnoi_internal.discoveryFailed); }; /// Callback that is invoked when discovery fails. box.dnoi_internal.discoveryFailed = function(message, identifier) { box.dnoi_internal.setVisualCue('failed'); if (message) { box.title = message; } } /// Callback that is invoked when discovery results are available. /// The JSON object containing the OpenID auth requests. /// The identifier that discovery was performed on. box.dnoi_internal.discoveryResult = function(discoveryResult, identifier) { // Deserialize the JSON object and store the result if it was a successful discovery. discoveryResult = eval('(' + discoveryResult + ')'); // Store the discovery results and added behavior for later use. box.dnoi_internal.authenticationRequests[identifier] = discoveryBehavior = new box.dnoi_internal.createDiscoveryInfo(discoveryResult, identifier); // Only act on the discovery event if we're still interested in the result. // If the user already changed the identifier since discovery was initiated, // we aren't interested in it any more. if (identifier == box.lastDiscoveredIdentifier) { discoveryBehavior.tryImmediate(); } } /// Invoked by RP web server when an authentication has completed. /// The duty of this method is to distribute the notification to the appropriate tracking object. box.dnoi_internal.processAuthorizationResult = function(resultUrl) { self.waiting_openidBox = null; //trace('processAuthorizationResult ' + resultUrl); var resultUri = new Uri(resultUrl); // Find the tracking object responsible for this request. var discoveryInfo = box.dnoi_internal.authenticationRequests[resultUri.getQueryArgValue('dotnetopenid.userSuppliedIdentifier')]; if (discoveryInfo == null) { trace('processAuthorizationResult called but no userSuppliedIdentifier parameter was found. Exiting function.'); return; } var opEndpoint = resultUri.getQueryArgValue("openid.op_endpoint") ? resultUri.getQueryArgValue("openid.op_endpoint") : resultUri.getQueryArgValue("dotnetopenid.op_endpoint"); var tracker = discoveryInfo.findByEndpoint(opEndpoint); //trace('Auth result for ' + tracker.host + ' received:\n' + resultUrl); if (isAuthSuccessful(resultUri)) { tracker.authSuccess(resultUri); discoveryInfo.successAuthData = resultUrl; var claimed_id = resultUri.getQueryArgValue("openid.claimed_id"); if (claimed_id && claimed_id != discoveryInfo.claimedIdentifier) { discoveryInfo.claimedIdentifier = resultUri.getQueryArgValue("openid.claimed_id"); trace('Authenticated as ' + claimed_id); } // visual cue that auth was successful box.dnoi_internal.claimedIdentifier = discoveryInfo.claimedIdentifier; box.dnoi_internal.setVisualCue('authenticated', tracker.endpoint, discoveryInfo.claimedIdentifier); if (box.dnoi_internal.onauthenticated) { box.dnoi_internal.onauthenticated(box); } if (box.dnoi_internal.submitPending) { // We submit the form BEFORE resetting the submitPending so // the submit handler knows we've already tried this route. if (box.dnoi_internal.submitPending == true) { box.parentForm.submit(); } else { box.dnoi_internal.submitPending.click(); } } } else { tracker.authFailed(); } box.dnoi_internal.submitPending = null; }; function isAuthSuccessful(resultUri) { if (isOpenID2Response(resultUri)) { return resultUri.getQueryArgValue("openid.mode") == "id_res"; } else { return resultUri.getQueryArgValue("openid.mode") == "id_res" && !resultUri.containsQueryArg("openid.user_setup_url"); } }; function isOpenID2Response(resultUri) { return resultUri.containsQueryArg("openid.ns"); }; box.onblur = function(event) { var discoveryInfo = box.dnoi_internal.authenticationRequests[box.value]; if (discoveryInfo == null) { if (box.value.length > 0) { box.dnoi_internal.performDiscovery(box.value); } else { box.dnoi_internal.setVisualCue(); } } else { if ((priorSuccess = discoveryInfo.findSuccessfulRequest())) { box.dnoi_internal.setVisualCue('authenticated', priorSuccess.endpoint, discoveryInfo.claimedIdentifier); } else { discoveryInfo.tryImmediate(); } } return true; }; box.onkeyup = function(event) { box.dnoi_internal.setVisualCue(); return true; }; box.getClaimedIdentifier = function() { return box.dnoi_internal.claimedIdentifier; }; // Restore a previously achieved state (from pre-postback) if it is given. var oldAuth = findOrCreateHiddenField().value; if (oldAuth.length > 0) { var oldAuthResult = new Uri(oldAuth); // The control ensures that we ALWAYS have an OpenID 2.0-style claimed_id attribute, even against // 1.0 Providers via the return_to URL mechanism. var claimedId = oldAuthResult.getQueryArgValue("dotnetopenid.claimed_id"); var endpoint = oldAuthResult.getQueryArgValue("dotnetopenid.op_endpoint"); // We weren't given a full discovery history, but we can spoof this much from the // authentication assertion. box.dnoi_internal.authenticationRequests[box.value] = new box.dnoi_internal.createDiscoveryInfo({ claimedIdentifier: claimedId, requests: [{ endpoint: endpoint }] }, box.value); box.dnoi_internal.processAuthorizationResult(oldAuthResult.toString()); } } function Uri(url) { this.originalUri = url; this.toString = function() { return this.originalUri; }; this.getAuthority = function() { var authority = this.getScheme() + "://" + this.getHost(); return authority; } this.getHost = function() { var hostStartIdx = this.originalUri.indexOf("://") + 3; var hostEndIndex = this.originalUri.indexOf("/", hostStartIdx); if (hostEndIndex < 0) hostEndIndex = this.originalUri.length; var host = this.originalUri.substr(hostStartIdx, hostEndIndex - hostStartIdx); return host; } this.getScheme = function() { var schemeStartIdx = this.indexOf("://"); return this.originalUri.substr(this.originalUri, schemeStartIdx); } this.trimFragment = function() { var hashmark = this.originalUri.indexOf('#'); if (hashmark >= 0) { return new Uri(this.originalUri.substr(0, hashmark)); } return this; }; this.appendQueryVariable = function(name, value) { var pair = encodeURI(name) + "=" + encodeURI(value); if (this.originalUri.indexOf('?') >= 0) { this.originalUri = this.originalUri + "&" + pair; } else { this.originalUri = this.originalUri + "?" + pair; } }; function KeyValuePair(key, value) { this.key = key; this.value = value; }; this.Pairs = new Array(); var queryBeginsAt = this.originalUri.indexOf('?'); if (queryBeginsAt >= 0) { this.queryString = url.substr(queryBeginsAt + 1); var queryStringPairs = this.queryString.split('&'); for (var i = 0; i < queryStringPairs.length; i++) { var pair = queryStringPairs[i].split('='); this.Pairs.push(new KeyValuePair(unescape(pair[0]), unescape(pair[1]))) } }; this.getQueryArgValue = function(key) { for (var i = 0; i < this.Pairs.length; i++) { if (this.Pairs[i].key == key) { return this.Pairs[i].value; } } }; this.containsQueryArg = function(key) { return this.getQueryArgValue(key); }; this.indexOf = function(args) { return this.originalUri.indexOf(args); }; return this; };