Achtung: Diese Seite enthält Partner- und Werbe-Links. Daher ist diese Seite im Gesamten als Werbeanzeige zu verstehen!
Durch Zufall entdeckt
Ich bin vor einigen Monaten auf den Artikel Screenshot unter Mac OS auf MacEinsteiger.de gestoßen. Als ich den dort stehenden Befehl defaults write com.apple.ScreenCapture type JPG; killAll SystemUIServer
kopieren wollte, hatte ich festgestellt, dass auch die URL zum Artikel heimlich in meine Zwischenablage kopiert wurde. Ich war erstaunt und habe mir das zur genaueren Untersuchung gemerkt. Nun hatte ich Zeit, mir die Vorlage im Detail anzugucken.
Schnell habe ich gemerkt, dass es ein onCopy
- Event gibt, das aufgerufen wird, wenn der Nutzer einen beliebigen Text kopiert. Das funktioniert anscheinend nicht nur in Verbindung mit Eingabefeldern, also <input>
- und <textarea>
-Elementen, sondern mit allem, was so auf der Webseite dargestellt wird. Mit folgendem Code-Schnipsel habe ich meine Theorie nochmals kurz bestätigt: Die Funktion wird immer aufgerufen, wenn ich irgendetwas auf meiner Webseite kopiere:
$('body').on('copy', '*', function () {
console.log('Wird immer aufgerufen, wenn der Nutzer etwas kopiert.');
});
Anschließend habe ich den Javascript-Quellcode von maceinsteiger.de genauer untersucht und bin schnell beim Copyright-Hinweis zu einem "Link Building Pro" stehen geblieben.
// Link Building Pro ver 1.0
// Copyright Latent Motion, at seox.org
Jetzt wird's technisch
Der Quellcode unter diesem Kommentar schien das zu sein, was ich suchte. Leider ist der Quellcode an dieser Stelle künstlich verkleinert und "hässlich gemacht", sodass mir die konkrete Funktionsweise nicht sofort klar war. Da die Google-Suche nach "Link Building Pro ver 1.0" nur wenige Beiträge liefert, die im Copyright genannte Webseite "seox.org" nicht mehr erreichbar ist und auch der gefundene Foren-Beitrag nur Beiträge aus 2012 enthält nahm ich an, dass es keine aktuelle Version vom ominösen "Link Building Pro" gibt.
// Link Building Pro ver 1.0
// Copyright Latent Motion, at seox.org
// Mod by F.Zsombor [CollisDam]
function init() {
// Options:
var useMetaKeyword = true; // Otherwise, page title
var minLength = 40; // Min selection chars
var useMetaAuthor = true; // Otherwise use domain
var addLinks = true; // Otherwise, just cite at end
var skip = new Array("home", "link", "click here"); // Don't link these (lowercase!)
var copyMessage = "NE MÁSOLJ!!!"; //Ide írd a figyelmeztetést (szöveg-idézőjelek közé)
var copyMessageNum = 20; //ennyiszer ismétlődjön (szám)
var keepOriginal = false; //megtartsa-e az eredeti szöveget is: true/false
function D(b,a,c){if(b.addEventListener)b.addEventListener(a,c,false);else b.attachEvent&&b.attachEvent("on"+a,c)}function o(b,a){if(typeof b=="undefined"||b==null||!RegExp)return false;a=new RegExp("(^|\\s)"+a+"(\\s|$)");if(typeof b=="string")return a.test(b);else if(typeof b=="object"&&b.className)return a.test(b.className);return false}function E(b,a){var c=false,j;for(j=b.parentNode;j!=undefined;){if(b.parentNode==e.body)break;else if(b.parentNode==a){c=true;break}j=j.parentNode}return c}function F(b){return b.replace(/^\s*/,"")}function G(b){return b.replace(/\s*$/,"")}function H(b){return G(F(b))}var I=new Array("home","link","click here"),e=document,x=window,t=e.getElementsByTagName("body")[0],p=e.getElementsByName("author"),i=e.getElementsByName("keywords"),q=x.location.toString(),u=e.title.toString(),d;if(!Array.indexOf)Array.prototype.indexOf=function(b,a){var c=-1;for(a=a|0;a<this.length||a==-1;a++)if(this[a]==b)c=a;return c};if(i.length>0&&useMetaKeyword){i=e.getElementsByName("keywords")[0].getAttribute("content").split(",");u=Math.floor(Math.random()*i.length);i=i[u].replace(/^\s*|\s*$/,"")}else i=u;p=(p.length>0&&useMetaAuthor)?p[0].getAttribute("content"):e.domain;var y="";for(var floodCopyMessage=1;floodCopyMessage<=copyMessageNum;floodCopyMessage++){y+=copyMessage+" "}if(/MSIE/g.test(navigator.userAgent))var v="msie";else if(/Safari/g.test(navigator.userAgent))v="safChrome";q=e.createElement("span");q.setAttribute("id","sasText");t.appendChild(q);d=e.getElementById("sasText");posType=document.all&&!window.opera&&!window.XMLHttpRequest?"absolute":"fixed";d.style.position=posType;d.style.top="0px";d.style.left="-9999px";D(t,"copy",function(){d.innerHTML=y;if(v=="msie"){for(var b=e.selection.createRange(),a=b.parentElement();a.nodeName!="BODY"&&!o(a,"lbExclude");)a=a.parentNode;if(o(a,"lbExclude"))return true;a=e.body.createTextRange();a.moveToElementText(d);var c=b.duplicate();c=c.htmlText;if(c.length>minLength){d.id="tempSasText";if(keepOriginal){d.innerHTML=c+y}(c=e.getElementById("sasText"))&&c.parentNode.removeChild(c);d.id="sasText";a.select()}}else{b=x.getSelection();for(a=b.anchorNode;a.nodeName!="BODY"&&!o(a,"lbExclude");)a=a.parentNode;if(o(a,"lbExclude"))return false;if(b==""&&v=="safChrome"){if(keepOriginal){d.innerHTML=t.innerHTML}a=document.createRange();b.removeAllRanges();a.selectNodeContents(d);b.addRange(a)}else if(b.toString().length>minLength){var j=e.getElementById("credit");a=b.getRangeAt(0);c=a.cloneContents();d.id="tempSasText";if(keepOriginal){d.insertBefore(c,j)}(c=e.getElementById("sasText"))&&c.parentNode.removeChild(c);d.id="sasText";b.removeAllRanges();a.selectNode(d);b.addRange(a)}}var w=[];a=d.getElementsByTagName("a");for(b=0;b<a.length;b++)w.push(a[b].href);if(addLinks){a=e.getElementsByTagName("a");for(b=0;b<a.length;b++){var r=a[b].href;if(w.indexOf(r)==-1)if(E(a[b],d)==false){var f=H(a[b].innerHTML).toLowerCase();if(skip.indexOf(f)==-1)if((new RegExp(e.domain,"g")).test(r)){var z=[];function n(g,k,l){for(var A=g.childNodes.length;A-->0;){var h=g.childNodes[A];if(h.nodeType===1)h.tagName.toLowerCase()!=="a"&&n(h,k,l);else if(h.nodeType===3)for(var m=h.data.length;1;){m=h.data.lastIndexOf(k,m);if(m===-1||z.indexOf(k.toLowerCase())!==-1)break;var B=/\w/;if(h.nodeValue.charAt(m-1).match(B)||h.nodeValue.charAt(m+f.length).match(B))break;l.call(window,h,m)}}}function s(g,k){g.splitText(k+f.length);var l=e.createElement("a");l.href=r;l.appendChild(g.splitText(k));g.parentNode.insertBefore(l,g.nextSibling);z.push(f.toLowerCase());w.push(r)}n(d,f,s);f=f.charAt(0).toUpperCase()+f.slice(1);n(d,f,s);f=f.toUpperCase();n(d,f,s);f=f.replace(/\w\S*/g,function(g){return g.charAt(0).toUpperCase()+g.substr(1).toLowerCase()});n(d,f,s)}}}}})}window.onload=init;
Reverse Engeneering
Nachdem ich auch nach längerer Suche keine alternativen Lösungen mit ähnlicher Funktion gefunden hatte, habe ich mich dazu entschlossen, das Script mit Hilfe von Reverse Engineering zu entschlüsseln. Schnell war klar: Der Code nutzt das schon angesprochene onCopy
-Ereignis und erstellt dann ein unsichtbares HTML-Element. Dann kopiert er den vom Nutzer ausgewählten Text sowie beliebigen weiteren Inhalt hinein. Zuletzt wird einfach die Textauswahl des Browsers auf dieses neu erstelle Objekt gesetzt.
Einzig fatal dabei ist, dass der Browser den Text aus der neuen Auswahl dann einfach kopiert. Das ist meiner Meinung nach ein Sicherheitsrisiko für den Nutzer, da so statt ein Beispielcode auch Schadcode kopiert werden könnte. Da aber die meisten Web-Browser aber schon seit 2015 das Verändern der Zwischenablage unterstützen, muss hier der Nutzer schlichtweg aufpassen und eben doch davon absehen, einen aus einer Webseite kopierten Shell-Befehl ohne weitere Prüfung direkt in eine Kommandozeile einzufügen.
Script aktualisiert
Da mir die Idee, dass ein Nutzer immer auch die Quelle des kopierten Textes mitkopiert gefiel, habe ich mir diese Funktionalität auf Basis der Vorlage nachgebaut. Mit den ebenfalls gefundenen Stackoverflow-Beiträgen Get selected text and selected nodes on a page? und Get Selected HTML in browser via Javascript war das dann auch relativ schnell erledigt: Der folgende Quellcode hängt bei jedem Kopiervorgang die URL der Webseite an den kopierten Text an. Dabei wird auch das Kopieren von HTML unterstützt:
$(document).ready(function () {
var jBody = $('body');
var body = jBody[0];
jBody.on('copy', '*', function (event) {
event.stopPropagation();
var appendText = '<br />' + window.location.href;
var selection = window.getSelection();
if (selection.rangeCount > 0) {
var originalRange = selection.getRangeAt(0);
var selectedText = originalRange.cloneContents();
var targetRaw = selection.anchorNode;
var elementToCopy = $('<div></div>')
.attr('id', 'copy-text')
.html(selectedText).append($('<p></p>').html(appendText))
.appendTo(jBody)
;
var elementToCopyInternal = elementToCopy[0];
if (body.createTextRange) {
var range1 = body.createTextRange();
range1.moveToElementText(elementToCopyInternal);
range1.select();
} else if (window.getSelection) {
var range2 = document.createRange();
range2.selectNodeContents(elementToCopyInternal);
selection.removeAllRanges();
selection.addRange(range2);
}
window.setTimeout(function () {
elementToCopy.remove();
selection.removeAllRanges();
selection.addRange(originalRange);
}, 50);
}
});
});
#copy-text
{
position: absolute;
top: 0;
left: 0;
opacity: 0.00000001;
width: 10;
height: 10;
font-size: 1px;
text-indent: -9999px;
}
Der Quellcode sollte in jedem modernen Browser funktionieren. Erfolgreich getestet habe ich es mit Google Chrome 58 und Mozilla Firefox 53 unter macOS.
Kommentare