vapour's blog

20Sep/110

非字母数字的JS(二)

在2011年的BlackHat DC 2011大会上Ryan Barnett给出了一段关于XSS的示例javascript代码: 

($=[$=[]][(__=!$+$)[_=-~-~-~$]+({}+$)[_/_]+($$=($_=!''+$)[_/_]+$_[+$])])()[__[_/_]+__[_+~$]+$_[_]+$$](_/_)

上面这段代码在浏览器中输出alert(1),这段代码中巧妙利用sort函数执行返回window对象来获取window对象,然后执行window['alert'](1)。关于这段代码的详细解释可查看这里

关于上面代码的实现技术原理,可以查看《非字母数字组成的JavaScript》。

由上面的代码,引发思考能不能输出数字(0-9)和全部26个字母,于是进行了下面尝试:

//获取数字
console.log(+[]); //0
console.log(-~[]); //1
console.log(-~-~[]); //2
console.log(-~-~-~[]); //3
console.log(-~-~-~-~[]); //4
console.log(-~-~-~-~-~[]); //5
console.log(-~-~-~-~-~-~[]); //6
console.log(-~-~-~-~-~-~-~[]); //7
console.log(-~-~-~-~-~-~-~-~[]); //8
console.log(-~-~-~-~-~-~-~-~-~[]); //9

通过上面这种方法,可以得到0-9,再组合0-9就可以得到任意数值了。

//获取字母
//[{}]+[] [object Object]
console.log(([{}]+[])[-~[]]); //o
console.log(([{}]+[])[-~-~[]]); //b
console.log(([{}]+[])[-~-~-~[]]); //j
console.log(([{}]+[])[-~-~-~-~[]]); //e
console.log(([{}]+[])[-~-~-~-~-~[]]); //c
console.log(([{}]+[])[-~-~-~-~-~-~[]]); //t

//![]+[] false
console.log((![] + [])[+[]]); //f
console.log((![] + [])[-~[]]); //a
console.log((![] + [])[-~-~[]]); //l
console.log((![] + [])[-~-~-~[]]); //s
console.log((![] + [])[-~-~-~-~[]]); //e

//!![]+[] true
console.log((!![]+[])[+[]]); //t
console.log((!![]+[])[-~[]]); //r
console.log((!![]+[])[-~-~[]]); //u
console.log((!![]+[])[-~-~-~[]]); //e

//({}[(!![]+[])[+[]]])+[] undefined
console.log((({}[(!![]+[])[+[]]])+[])[-~[]]); //n
console.log((({}[(!![]+[])[+[]]])+[])[-~-~[]]); //d
console.log((({}[(!![]+[])[+[]]])+[])[-~-~-~-~-~[]]); //i

经过尝试,暂时只能得到a b c d e f i j l n o r s t u共15个字母。欢迎补充

10Aug/110

轻量级模板系统

开发问卷系统时,开发了一个简单的模板管理功能。实现了简单的变量替换、循环、自定义函数、并提供了循环变量tmCount(从0开始)和tmNumber(从1开始)。不多说,先上代码:

var templateManager = (function () {
    var templates = {},
        tmp,
        supplant = {
            init: function (str, params) {
                var value, prop, tmp, len;
                for (prop in params) {
                    if (params.hasOwnProperty(prop)) {
                        value = params[prop];
                        switch (Object.prototype.toString.call(value)) {
                            case '[object Number]':
                            case '[object String]':
                                tmp = {};
                                tmp[prop] = value;
                                str = supplant.replaceString(str, tmp);
                                break;
                            case '[object Function]':
                                str = supplant.replaceFunc(str, prop, value, params);
                                break;
                            case '[object Array]':
                                str = supplant.replaceArray(str, prop, value);
                                break;
                            default:
                                break;
                        }
                    }
                }
                return str;
            },
            replaceString: function (str, params) {
                var prop;
                for (prop in params) {
                    if (params.hasOwnProperty(prop)) {
                        str = str.split('{' + prop + '}').join(params[prop]);
                    }
                }
                return str;
            },
            replaceFunc: function (str, prop, func, item) {
                return str.split('{' + prop + '}').join(func(item));
            },
            replaceArray: function (str, prop, data) {
                var i = 0,
                    left,
                    middle,
                    right,
                    s = '{#' + prop + '}',
                    e = '{/' + prop + '}',
                    len = data.length,
                    start = 0,
                    end = 0;
                    
                start = str.indexOf(s);
                end = str.indexOf(e, start);
                left = str.slice(0, start);
                middle = str.slice(start + s.length, end);
                right = str.slice(end + e.length);
                if (len > 0 && start > -1) {
                    for (; i < len; ++i) {
                        data[i].tmCount = i;
                        data[i].tmNumber = i + 1;
                        if (data[i].content !== null) { //如果content属性不等于null
                            left += supplant.init(middle, data[i]);
                        } else {
                            left += data[i].replace;
                        }
                    }
                    str = left + right;
                    
                    if (str.indexOf(s) > -1) {//如果包括多个循环
                        str = supplant.replaceArray(str, prop, data);
                    }
                }
                
                if (len == 0 && start > -1) {
                    str = left + right;
                }
                
                return str;
            }
        };
    
    return {
        render: function (name, params) {
            if (typeof templates[name] !== 'string') {
                throw 'Template ' + name + ' not found!';
            }
            tmp = name;
            return supplant.init(templates[name], params);
        },
        defineTemplate: function (name, template) {
            if (typeof template == 'string') {
                templates[name] = template;
            } else { //数组
                templates[name] = template.join('');
            }
        }
    };
})();

上面代码中定义一个templateManager对象用来管理模板,提供了两个接口:

  1. defineTemplate 用来定义模板
  2. render 用来渲染模板

内部私有的supplant对象实现模板引擎,提供了替换变量、替换循环、替换函数的功能。

文档和示例:

//替换变量
templateManager.defineTemplate('profile', '姓名:{name},blog:{blog}');
templateManager.render('profile', {name: 'vapour', blog: 'http://dovapour.info/'});
//结果 姓名:vapour,blog:http://dovapour.info/

//实现循环
templateManager.defineTemplate('website', [
    '<ul>',
        '{#webs}<li><a href="{url}">{name}<_buff_29_buff_li>{/webs}',
    '<ul>'
]);
templateManager.render('website', {
    webs: [{
        name: '百度',
        url: 'http://www.baidu.com/',
        getLen: function (item) {
            return item.url.length;
        }
    }, {
        name: '淘宝',
        url: 'http://www.taobao.com/',
        getLen: function (item) {
            return item.url.length;
        }
    }, {
        name: '腾讯',
        url: 'http://www.qq.com/',
        getLen: function (item) {
            return item.url.length;
        }
    }, {
        name: '网易',
        url: 'http://163.com/',
        getLen: function (item) {
            return item.url.length;
        }
    }, {
        name: '新浪',
        url: 'http://www.sina.com/',
        getLen: function (item) {
            return item.url.length;
        }
    }]
});
/*
<ul>
    <li><a href="http://www.baidu.com/">百度</a>,url长度21</li>
    <li><a href="http://www.taobao.com/">淘宝</a>,url长度22</li>
    <li><a href="http://www.qq.com/">腾讯</a>,url长度18</li>
    <li><a href="http://163.com/">网易</a>,url长度15</li>
    <li><a href="http://www.sina.com/">新浪</a>,url长度20</li>
<ul>
*/

Tagged as: No Comments
22Jul/110

ie中的跨域和ckeditor

在IE中修改了document.domain进行提权后(无论修改前后域名是否不同),iframe都会出现跨域问题。

ckeditor中检查跨域的代码,是根据判断document.domain和window.location.hostname是否相同来来判断跨域的。

isCustomDomain: function () {
   if (!this.ie) return false;
   var g = document.domain,
   h = window.location.hostname;
   return g != h && g != '[' + h + ']';
}

当我们执行在页面执行 document.domain = document.domain 后,执行 isCustomDomain 会返回false,这时ie会因为跨域,报拒绝访问的错误。

所以当我们,在修改document.domain时要进行判断,当新域和旧域不同时再修改document.domain

var oldDomain = document.domain,
    newDomain = 'xxx.com';
if (newDomain != oldDomain) {
   document.domain = newDomain;
}

 

7Jul/110

跨浏览器实现全选功能

跨浏览器实现全选功能,IE一行代码就OK,标准浏览器就比较麻烦了,需要使用selection、range、addRange实现。

var selectAll = (function () {
   if (arale.isIE()) { //IE
      return function (win) {
         win = win || window;
         win.document.body.createControlRange().execCommand('SelectAll');
      }
   } else { //标准浏览器
      return function (win) {
         var s, r;
         win = win || window;
         doc = win.document;
         s = win.getSelection();
         r = doc.createRange();

         r.selectNode(doc.body);
         s.addRange(r);
      }
   }
})();

19Apr/110

使用动态类型、对象构造而非继承

今天读了OOP The Good Parts: Message Passing, Duck Typing, Object Composition, and not Inheritance,作者提倡使用动态类型(duck typing)、对象构造来代替继承。

关于动态类型的示例代码:

function bad (foo) {
    if ( ! (foo instanceof Bar) ) {
        throw new TypeError("This is an annoying restriction!");
    }
    return foo.baz(foo.quux(10));
}

function good (foo) {
    if ( !foo.baz || !foo.quux ) {
        throw new TypeError("We need foo to have baz and quux methods.");
    }
    return foo.baz(foo.quux(10));
}

下面是作者给出使用动态类型和对象构造的一个操作DOM的示例:

var quacksLike = function (obj, definition) {
    var k, ctor;
    for ( k in definition ) {
        ctor = definition[k];
        if ( ctor === Number ) {
            if ( Object.prototype.toString.call(obj[k]) !== "[object Number]" || isNaN(obj[k]) ) {
                return false;
            }
        } else if ( ctor === String ) {
            if ( Object.prototype.toString.call(obj[k]) !== "[object String]" ) {
                return false;
            }
        } else if ( ! (obj[k] instanceof ctor) ) {
            return false;
        }
    }
    return true;
};
Object.combine = function () {
    var newObj = {},
        i = 0,
        args = Array.prototype.slice.call(arguments),
        len = args.length;
    function copyProperty(k) {
        newObj[k] = args[i][k];
    }
    for ( ; i < len; i++ ) {
        Object.getOwnPropertyNames(args[i]).forEach(copyProperty);
    }
    return newObj;
};
window.$ = (function () {
    var undef, listen, customAPI;
    
    // Private function to turn 'thing' in to an array,
    // optionally slicing the first 'n' elems off.
    function toArray(thing, n) {
        return Array.prototype.slice.call(thing, n || 0);
    }

    // Private function to partially apply the first n arguments
    // to 'fn'.
    function curry (fn) {
        var args = toArray(arguments, 1);
        return function () {
            return fn.apply(this, args.concat(toArray(arguments)));
        };
    }

    // Private function to set the attribute 'attr' of a single
    // DOM node 'node' to 'val'.
    function nodeAttributeSetter (attr, val, node) {
        node.setAttribute(attr, val);
    }

    // Private function to get the value of the attribute 'attr'
    // on the DOM node 'node'.
    function nodeAttributeGetter (attr, node) {
        return node.getAttribute(attr);
    }

    // Do the work to determine the correct method for attaching
    // event listeners only once by using 'quacksLike' and an
    // immediately invoked function.
    listen = (function () {
        var w3c = {
                addEventListener: Function
            },
            ie = {
                attachEvent: Function
            };

        if ( quacksLike(document.body, w3c) ) {
            return function (event, fn, node) {
                node.addEventListener(event, fn, false);
            };
        } else if ( quacksLike(document.body, ie) ) {
            return function (event, fn, node) {
                node.attachEvent("on" + event, fn);
            };
        } else {
            throw new TypeError("Do not know how to attach event listeners.");
        }
    }());

    // This object is a mixin to be composed with the NodeList
    // results of querying the DOM by CSS selector. It provides
    // handy methods for getting and setting attributes,
    // attaching event listeners, and performing sub-queries by
    // CSS on these elements.
    customAPI = {
        attr: function (attr, val) {
            if ( val !== undef ) {
                this.forEach(curry(nodeAttributeSetter, attr, val));
                return this;
            } else {
                return this.length > 0 ? nodeAttributeGetter(attr, this[0]) : "";
            }
        },
        on: function (event, fn) {
            this.forEach(curry(listen, event, fn));
            return this;
        },

        find: function (selector) {
            var res = [];
            this.forEach(function (node) {
                res.push.apply(res, toArray(node.querySelectorAll(selector)));
            });
            return Object.combine(res, customAPI);
        }

    };

    return function (selector, context) {
        context = context || document;
        return Object.combine(Array.prototype, context.querySelectorAll(selector), customAPI);
    };

}());

//The mini DOM library might be used like this:

// The following two forms are equivalent.
// They select the same elements.
$("div").find("p");
$("div p");

// Disable all links.
$("a").on("click", function (event) {
  event.preventDefault();
  alert("Don't leave me all alone!");
});

// Get the id of the first paragraph.
$("p").attr("id");

// Disable all input elementss.
$(input").attr("disabled", "disabled");

// Do stuff with each matched element.
$("div.foo").forEach(function (el) {
  doStuffWith(el);
});

25Mar/110

CSS3动画实现mouseLeave和mouseEnter的不同效果

Eric在Inconsistent Transitions 中指出CSS3动画在浏览器中的不一致。

div:hover span {
    transition: 1s transform;
    transform: rotate(270deg);

}

上面代码只在鼠标移动到div上时有过渡动画(transition),在鼠标离开div时没有过渡动画。并给出如下解决文案:

div span {
    transition: 1s transform;
}
div:hover span {

    transform: rotate(270deg);
}

这样,当鼠标进入和离开div时,都有过渡动画效果。其实我们可以进一步实现鼠标进入(mouseEnter)和鼠标离开(mouseLeave)的不同效果。

/*mouseleave*/
div span {

    transition: 3s transform;
}
/*mouseenter*/
div:hover span {

    transform: rotate(270deg);
    transition: 1s transform;

}

这样,就实现了mouseLeave和mouseEnter的不同过渡效果。

17Mar/112

label导致冒泡事件的诡异问题

今天遇到一个很怪异的问题,给label标签绑定了click事件,但是每次单击时,click事件都会执行两次。开始以为自己的代码有问题,反复痛苦的检查了N久以后,才发现自己代码没问题。根本原因在于事件的冒泡机制和label标签共同作用引起的。

<ul id="sdfsde" class="multi-list">
    <li><label for="q1"><input id="q1" type="text" value="" />工商</label></li>
    <li><label for="q2"><input id="q2" type="text" value="" />农业</label></li>
    <li><label for="q3"><input id="q3" type="text" value="" />招商</label></li>
    <li><label for="q4"><input id="q4" type="text" value="" />建设</label></li>
    <li><label for="q5"><input id="q5" type="text" value="" />浦发</label></li>
</ul>

执行两次的过程如下:

  1. 单击label标签,第一次执行click事件
  2. 由于label标签的特殊性,事件向下传递到input标签
  3. 事件开始冒泡几上传递
  4. 冒泡到label标签时,第二次执行click事件

为了更便于说明上面的过程,请看下面的示例:

<label id="lbUser">
    <input id="txtUser" type="text" value="单击这里alert二次" />
    单击这里alert三次
</label>
<script type="text/javascript">
$('lbUser').click(function(){
    alert('单击label');
});
$('txtUser').click(function(){
    alert('单击input');
});
</script>

当点击label中的文字时,依次弹出:

  1. alert('单击label')
  2. alert('单击input');
  3. alert('单击label');

当直接点击input输入框时,依次弹出:

  1. alert('单击input');
  2. alert('单击label');

总结:这个问题让自己纠结了,很长时间。主要原因时没有考虑到冒泡,需要注意的这里我们注册的事件都是绑定在冒泡期间,但是因为label(会自动将焦点转到和标签相关的表单控件上),所以导致了这个意外。

Tagged as: , 2 Comments
11Mar/111

非字母数字的JavaScript

由非字母数字组成的JavaScript,这是几年前一个日本安全专家Yosuke Hasegawa在slackers论坛上提出的。这种技术主要是利用JavaScript的数据类型自动转换的特性,例如布尔类型true、false可以转换成字符类型"true"、"false"。

现在我们利用上面这种技术来获取字母o,我们可以利用对象调用toString方法转换成字符串[object  Object]来得到字母o。

1 首页我们需要获得对象字面量[object Object]:

 [{}]+[]//[object Object]

 

2 得到数字0

+[]//0 

 

3 得到数字1,在0的基础上增加1

++[+[]][+[]]//1 

 

4最后合并上面代码,得到字母o

 alert([[{}]+[]][+[]][++[+[]][+[]]])//"o"

 

如果对这方面感兴趣,可以阅读:

YAUC Less chars needed to run arbitrary JS code = 6! (JS GREAT WALL) 
http://sla.ckers.org/forum/read.php?24,32930

Diminuitive NonAlNum JS - Arbitrary:-
http://sla.ckers.org/forum/read.php?24,35081

Java/script: no alnum cheat sheets:-
http://sla.ckers.org/forum/read.php?24,33349

Diminutive JS Code Challenge, from OWASP :-
http://sla.ckers.org/forum/read.php?24,30015

10Mar/110

数组的小技巧

JavaScript array “extras” in detail 介绍了ES5对数组原型对象扩充的方法:forEach、map、filter、some、every、indexOf、lastIndexOf、reduce、reduceRight。这些方法不仅可以应用到数组,还可应用到拥有length属性,并且可以数字索引的对象上,例如String,arguments,DOM nodes collection等。

// get the reference to the map method
var map = Array.prototype.map;

// and call it for a string
var hello = map.call("hello world", function (char) {
  return char + "*";
});

alert(hello.join("")); // "h*e*l*l*o* *w*o*r*l*d*"

 

除了上面这些ES5的方法,join方法也可应用到拥有length属性,并且可以数字索引的对象上,示例:

var s = 'abcd'
    f = Array.prototype.join;
var str = f.call(s, '-');
alert(str);// a-b-c-d

 

文中还有一个数组方法重用字符串方法的例子:

// reuse "toUpperCase" method
var toUpperCase = String.prototype.toUpperCase;
var upper = toUpperCase.apply(["foo", "bar"]).split(",");
alert(upper); // ["FOO", "BAR"]

 

下面是John Resig 给出的获取数组最大与最小值的方法,很有创意:

function smallest(array){
    return Math.min.apply( Math, array );
}
function largest(array){
    return Math.max.apply( Math, array );
}
Tagged as: No Comments
7Mar/110

关于typeof运算

在JavaScript中,变量可以不声明直接赋值使用,但不能直接引用,否则会出错,看下面代码:

console.log(foo); //reference error
if(foo){ //reference error
    alert('true');
}
typeof(foo); //undefined

看了上面代码对于进行typeof运算为什么不报错,而是返回undefined,这是因为typeof操作并不调用GetValue。ECMA中关于typeof操作的描述:

The production UnaryExpression : typeof UnaryExpression is evaluated as follows:

  1. Let val be the result of evaluating UnaryExpression
  2. If Type(val) is Reference, then

    • If IsUnresolvableReference(val) is true, return "undefined"
    • Let val be GetValue(val)
  3. Return a String determined by Type(val) according to Table
typeof Operator Results
typeof val result
undefined "undefined"
Null "object"
Boolean "boolean"
Number "number"
String "string"
Object (native and does not implement [[Call]]) "object"
Object (native or host and does implement [[Call]]) "function"
Object (host and does not implement [[Call]]) Implementation-defined except may not be "undefined", "boolean", "number", or "string".

 

Tagged as: , No Comments