<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>润物无声 &#187; Web设计</title>
	<atom:link href="http://blog.zhourunsheng.com/category/%e8%bd%af%e4%bb%b6%e5%bc%80%e5%8f%91/web%e8%ae%be%e8%ae%a1/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.zhourunsheng.com</link>
	<description>天空一朵雨做的云</description>
	<lastBuildDate>Sat, 08 May 2021 05:17:21 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.41</generator>
	<item>
		<title>迁移博客图片资源从SAE到七牛</title>
		<link>http://blog.zhourunsheng.com/2014/05/%e8%bf%81%e7%a7%bb%e5%8d%9a%e5%ae%a2%e5%9b%be%e7%89%87%e8%b5%84%e6%ba%90%e4%bb%8esae%e5%88%b0%e4%b8%83%e7%89%9b/</link>
		<comments>http://blog.zhourunsheng.com/2014/05/%e8%bf%81%e7%a7%bb%e5%8d%9a%e5%ae%a2%e5%9b%be%e7%89%87%e8%b5%84%e6%ba%90%e4%bb%8esae%e5%88%b0%e4%b8%83%e7%89%9b/#comments</comments>
		<pubDate>Wed, 21 May 2014 01:57:02 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[Web设计]]></category>
		<category><![CDATA[SAE]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[七牛]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1880</guid>
		<description><![CDATA[<p>博客的很多图片资料存放在 SAE 上面，鉴于七牛的静态资源存储不错，计划迁移到七牛云存储上面，进而方便统一管理 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/05/%e8%bf%81%e7%a7%bb%e5%8d%9a%e5%ae%a2%e5%9b%be%e7%89%87%e8%b5%84%e6%ba%90%e4%bb%8esae%e5%88%b0%e4%b8%83%e7%89%9b/">迁移博客图片资源从SAE到七牛</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>博客的很多图片资料存放在 SAE 上面，鉴于七牛的静态资源存储不错，计划迁移到七牛云存储上面，进而方便统一管理。<br />
【下载 SAE Storage 中的资料】</p>
<ul>
<li>所需工具：Cyberduck</li>
<li>配置</li>
</ul>
<blockquote><p>服务器： auth.sinas3.com<br />
端口：443<br />
用户名：应用AccessKey<br />
密码：应用SecretKey</p></blockquote>
<blockquote><p><img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/cyberduck.png" alt="" /></p></blockquote>
<p><span id="more-1880"></span><br />
【上传到七牛】</p>
<ul>
<li>所需工具：qiniu-devtools-windows_386 [qrsync.exe]</li>
<li>配置, 新建 conf.json 文件</li>
</ul>
<blockquote><p>access_key： 应用AccessKey<br />
secret_key：应用SecretKey<br />
bucket：bucket 名称<br />
sync_dir：本地同步文件夹</p></blockquote>
<blockquote><p><img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/confjson.png" alt="" /></p></blockquote>
<ul>
<li>执行 qrsync.exe conf.json 即可</li>
</ul>
<p>【外链切换】</p>
<ul>
<li>原外链地址：</li>
</ul>
<blockquote><p><span style="color: #ff0000;">http://carey-wordpress.stor.sinaapp.com</span>/uploads/2011/06/The-Moon-150x150.jpg</p></blockquote>
<ul>
<li>新外链地址：</li>
</ul>
<blockquote><p><span style="color: #ff0000;">http://carey.u.qiniudn.com</span>/uploads/2011/06/The-Moon-150x150.jpg</p></blockquote>
<p>【数据库相关】<br />
1. 查询包含外链的相关文章<br />
SELECT * FROM  `wp_posts` WHERE post_content LIKE  '%http://carey-wordpress.stor.sinaapp.com/%'</p>
<p>2. 指定文章替换<br />
UPDATE wp_posts SET post_content = replace(post_content, 'http://carey-wordpress.stor.sinaapp.com/', 'http://carey.u.qiniudn.com/') WHERE ID=24</p>
<p>3. 全局替换<br />
UPDATE wp_posts SET post_content = replace(post_content, 'http://carey-wordpress.stor.sinaapp.com/', 'http://carey.u.qiniudn.com/')</p>
<p>【资料相关】</p>
<ul>
<li>SAE 地址：<a href="http://sae.sina.com.cn/">http://sae.sina.com.cn/</a></li>
<li>SAE SVN 地址：<a href="https://svn.sinaapp.com/carey">https://svn.sinaapp.com/carey</a></li>
<li>七牛官方：<a href="https://portal.qiniu.com/">https://portal.qiniu.com/</a></li>
</ul>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/05/%e8%bf%81%e7%a7%bb%e5%8d%9a%e5%ae%a2%e5%9b%be%e7%89%87%e8%b5%84%e6%ba%90%e4%bb%8esae%e5%88%b0%e4%b8%83%e7%89%9b/">迁移博客图片资源从SAE到七牛</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2014/05/%e8%bf%81%e7%a7%bb%e5%8d%9a%e5%ae%a2%e5%9b%be%e7%89%87%e8%b5%84%e6%ba%90%e4%bb%8esae%e5%88%b0%e4%b8%83%e7%89%9b/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>JavaScript之变量和函数提升</title>
		<link>http://blog.zhourunsheng.com/2013/09/javascript%e4%b9%8b%e5%8f%98%e9%87%8f%e5%92%8c%e5%87%bd%e6%95%b0%e6%8f%90%e5%8d%87/</link>
		<comments>http://blog.zhourunsheng.com/2013/09/javascript%e4%b9%8b%e5%8f%98%e9%87%8f%e5%92%8c%e5%87%bd%e6%95%b0%e6%8f%90%e5%8d%87/#comments</comments>
		<pubDate>Fri, 06 Sep 2013 23:54:32 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[Web设计]]></category>
		<category><![CDATA[JavaScript]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1838</guid>
		<description><![CDATA[<p>JS代码有一个特殊的规则，那就是hoisted提升规则，函数的调用可以放在函数实现的前面，但是仅限于函数是以非 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/09/javascript%e4%b9%8b%e5%8f%98%e9%87%8f%e5%92%8c%e5%87%bd%e6%95%b0%e6%8f%90%e5%8d%87/">JavaScript之变量和函数提升</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>JS代码有一个特殊的规则，那就是<strong>hoisted提升规则</strong>，函数的调用可以放在函数实现的前面，但是仅限于函数是以非表达式的场合声明实现的，那么变量的提升又有什么不同呢，在开发过程中还会出现哪些经常犯的错误，本文对此进行了介绍。</p>
<p>One of the trickier aspects of JavaScript for new JavaScript developers is the fact that variables and functions are "hoisted." Rather than being available after their declaration, they might actually be available beforehand. How does that work? Let's take a look at variable hoisting first.<span id="more-1838"></span></p>
<pre>// ReferenceError: noSuchVariable is not defined
console.log(noSuchVariable);</pre>
<p>This is more or less what one would expect. An error is thrown when you try to access the value of a variable that doesn't exist. But what about this case?</p>
<pre>// Outputs: undefined
console.log(declaredLater);

var declaredLater = "Now it's defined!";

// Outputs: "Now it's defined!"
console.log(declaredLater);</pre>
<p>What is going on here? It turns out that JavaScript treats variables which will be declared later on in a function differently than variables that are not declared at all. Basically, the JavaScript interpreter "looks ahead" to find all the variable declarations and "hoists" them to the top of the function. Which means that the example above is equivalent to this:</p>
<pre>var declaredLater;

// Outputs: undefined
console.log(declaredLater);

declaredLater = "Now it's defined!";

// Outputs: "Now it's defined!"
console.log(declaredLater);</pre>
<p>One case where this is particularly likely to bite new JavaScript developers is when reusing variable names between an inner and outer scope. For example:</p>
<pre>var name = "Baggins";

(function () {
    // Outputs: "Original name was undefined"
    console.log("Original name was " + name);

    var name = "Underhill";

    // Outputs: "New name is Underhill"
    console.log("New name is " + name);
})();</pre>
<p>In cases like this, the developer probably expected <em>name</em> to retain its value from the outer scope until the point that <em>name</em> was declared in the inner scope. But due to hoisting, <em>name</em> is <em>undefined</em>instead.</p>
<p>Because of this behavior JavaScript linters and style guides often recommend putting all variable declarations at the top of the function so that you won't be caught by surprise.</p>
<p>So that covers variable hoisting, but what about function hoisting? Despite both being called "hoisting," the behavior is actually quite different. Unlike variables, a function declaration doesn't just hoist the function's name. It also hoists the actual function definition.</p>
<pre>// Outputs: "Yes!"
isItHoisted();

function isItHoisted() {
    console.log("Yes!");
}</pre>
<p>As you can see, the JavaScript interpreter allows you to use the function before the point at which it was declared in the source code. This is useful because it allows you to express your high-level logic at the beginning of your source code rather than the end, communicating your intentions more clearly.</p>
<pre>travelToMountDoom();
destroyTheRing();

function travelToMountDoom() { /* Traveling */ }
function destroyTheRing() { /* Destruction */ }</pre>
<p>However, <strong>function definition hoisting only occurs for function declarations</strong>, not function expressions. For example:</p>
<pre>// Outputs: "Definition hoisted!"
definitionHoisted();

// TypeError: undefined is not a function
definitionNotHoisted();

function definitionHoisted() {
    console.log("Definition hoisted!");
}

var definitionNotHoisted = function () {
    console.log("Definition not hoisted!");
};</pre>
<p>Here we see the interaction of two different types of hoisting. Our variable <em>definitionNotHoisted</em>has its declaration hoisted (thus it is <em>undefined</em>), but not its function definition (thus the<em>TypeError</em>.)</p>
<p>You might be wondering what happens if you use a named function expression.</p>
<pre>// ReferenceError: funcName is not defined
funcName();

// TypeError: undefined is not a function
varName();

var varName = function funcName() {
    console.log("Definition not hoisted!");
};</pre>
<p>As you can see, the function's name doesn't get hoisted if it is part of a function expression.</p>
<p>And that is how variable and function hoisting works in JavaScript.</p>
<p>引用：<a href="http://designpepper.com/blog/drips/variable-and-function-hoisting" target="_blank">http://designpepper.com/blog/drips/variable-and-function-hoisting</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/09/javascript%e4%b9%8b%e5%8f%98%e9%87%8f%e5%92%8c%e5%87%bd%e6%95%b0%e6%8f%90%e5%8d%87/">JavaScript之变量和函数提升</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/09/javascript%e4%b9%8b%e5%8f%98%e9%87%8f%e5%92%8c%e5%87%bd%e6%95%b0%e6%8f%90%e5%8d%87/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>解读JavaScript的Scope和Context</title>
		<link>http://blog.zhourunsheng.com/2013/08/%e8%a7%a3%e8%af%bbjavascript%e7%9a%84scope%e5%92%8ccontext/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/%e8%a7%a3%e8%af%bbjavascript%e7%9a%84scope%e5%92%8ccontext/#comments</comments>
		<pubDate>Sat, 31 Aug 2013 01:47:22 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[Web设计]]></category>
		<category><![CDATA[JavaScript]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1831</guid>
		<description><![CDATA[<p>本文详细解读了Javascript语言中的作用域Scope和上下文Context。JS是一种特殊的解释型语言， [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e8%a7%a3%e8%af%bbjavascript%e7%9a%84scope%e5%92%8ccontext/">解读JavaScript的Scope和Context</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>本文详细解读了Javascript语言中的作用域Scope和上下文Context。JS是一种特殊的解释型语言，尤其是闭包的概念，使得JS更灵活，还有事件的绑定bind，this指针的用法等，相信本文有所帮助。</p>
<p>JavaScript’s implementation of scope and context is a unique feature of the language, in part because it is so flexible. Functions can be adopted for various contexts and scope can be encapsulated and preserved. These concepts are behind some of the most powerful design patterns JavaScript has to offer. However, this is also a tremendous source of confusion amongst developers, and for good reason. The following is a comprehensive explanation of scope and context in JavaScript, the difference between them, and how various design patterns make use of them.<span id="more-1831"></span></p>
<h2>Context vs. Scope</h2>
<p>The first important thing to clear up is that context and scope are not the same. I have noticed many developers over the years often confuse the two terms, incorrectly describing one for the other. To be fair, the terminology has become quite muddled over the years.</p>
<p>Every function invocation has both a scope and a context associated with it. Fundamentally, scope is <em>function-based</em> while context is <em>object-based</em>. In other words, scope pertains to the variable access of a function when it is invoked and is unique to each invocation. Context is always the value of the <em>this</em> keyword, which is a reference to the object that “owns” the currently executing code.</p>
<h3>Variable Scope(变量作用域)</h3>
<p>A variable can be defined in either local or global scope, which establishes the variables’ accessibility from different scopes during runtime. Any defined global variable, meaning any variable declared outside of a function body, will live throughout runtime and can be accessed and altered in any scope. Local variables exist only within the function body of which they are defined and will have a different scope for every call of that function. There it is subject for value assignment, retrieval, and manipulation only within that call and is not accessible outside of that scope.</p>
<p>JavaScript presently does not support block scope which is the ability to define a variable to the scope of an if statement, switch statement, for loop, or while loop. This means the variable will not be accessible outside the opening and closing curly braces of the block. Currently any variables defined inside a block are accessible outside the block. However, this is soon to change, the <em>let</em> keyword has officially been added to the ES6 specification. It can be used as an alternative to the <em>var</em> keyword in order to support the declaration of block scope local variables.</p>
<h3>What is “this” Context（this指针上下文）</h3>
<p>Context is most often determined by how a function is invoked. When a function is called as a method of an object, <em>this</em> is set to the object the method is called on:</p>
<pre>var object = {
    foo: function(){
        alert(this === object); 
    }
};

object.foo(); // true</pre>
<p>The same principle applies when invoking a function with the <em>new</em> operator to create an instance of an object. When invoked in this manner, the value of <em>this</em> within the scope of the function will be set to the newly created instance:</p>
<pre>function foo(){
    alert(this);
}

foo() // window
new foo() // foo</pre>
<p>When called as an unbound function, <em>this</em> will default to the global context or window object in the browser. However, if the function is executed in <em>strict mode</em>, the context will default to undefined.</p>
<h2>Execution Context and Scope Chain（运行时上下文和作用域链）</h2>
<p>JavaScript is a single threaded language, meaning only one thing can be done at a time in the browser. When the JavaScript interpreter initially executes code, it first enters into a global execution context by default. Each invocation of a function from this point on will result in the creation of a new execution context.</p>
<p>This is where confusion often sets in, the term “execution context” is actually for all intents and purposes referring to scope and not context as previously discussed. It is an unfortunate naming convention, however it is the terminology as defined by the ECMAScript specification, so were kinda stuck with it.</p>
<p>Each time a new execution context is created, it is appended to the top of what is called a scope chain, sometimes referred to as an execution or call stack. The browser will always execute the current execution context that is atop the scope chain. Once completed, it will be removed from the top of the stack and control will return to the execution context below. For example:</p>
<pre>function first(){
    second();
    function second(){
        third();
        function third(){
            fourth();
            function fourth(){
                // do something
            }
        }
    }   
}
first();</pre>
<p>Running the preceeding code will result in the nested functions being executed all the way down to the <em>fourth</em> function. At this point the scope chain would be, from top to bottom: fourth, third, second, first, global. The <em>fourth</em> function would have access to global variables and any variables defined within the <em>first</em>, <em>second</em> and <em>third</em> functions as well as the functions themselves. Once the <em>fourth</em> function has completed execution, it will be removed from the scope chain and execution will return to the <em>third</em> function. This process continues until all code has completed executing.</p>
<p>Name conflicts amongst variables between different execution contexts are resolved by climbing up the scope chain, moving locally to globally. This means that local variables with the same name as variables higher up the scope chain take precedence.</p>
<p>An execution context can be divided into a creation and an execution phase. In the creation phase, the interpreter will first create a <em>variable object</em> (also called an activation object) that is composed of all the variables, function declarations and arguments defined inside the execution context. From there the scope chain is initialized next and the value of <em>this</em> is determined last. Then in the execution phase, code is interpreted and executed.</p>
<p>To put it simply, each time you attempt to access a variable within a function’s execution context, the look-up process will always begin with its own variable object. If the variable is not found in the variable object, the search continues into the scope chain. It will climb up the scope chain examining the variable object of every execution context looking for a match to the variable name.</p>
<h2>Closures（闭包）</h2>
<p>A closure is formed when a nested function is made accessible outside of the function in which it was defined, so that it may be executed after the outer function has returned. It maintains access to the local variables, arguments, and inner function declarations of its outer function. Encapsulation allows us to hide and preserve the execution context from outside scopes while exposing a public interface and thus is subject to further manipulation. A simple example of this looks like the following:</p>
<pre>function foo(){
    var local = 'private variable';
    return function bar(){
        return local;
    }
}

var getLocalVariable = foo();
getLocalVariable() // private variable</pre>
<p>One of the most popular types of closures is what is widely known as the module pattern. It allows you to emulate public, private and privileged members:</p>
<pre>var Module = (function(){
    var privateProperty = 'foo';

    function privateMethod(args){
        //do something
    }

    return {

        publicProperty: "",

        publicMethod: function(args){
            //do something
        },

        privilegedMethod: function(args){
            privateMethod(args);
        }
    }
})();</pre>
<p>The module acts as if it were a singleton, executed as soon as the compiler interprets it, hence the opening and closing parenthesis at the end of the function. The only available members outside of the execution context of the closure are your public methods and properties located in the return object (<em>Module.publicMethod</em> for example). However, all private properties and methods will live throughout the life of the application as the execution context is preserved, meaning variables are subject to further interaction via the public methods.</p>
<p>Another type of closure is what is called an immediately-invoked function expression (IIFE) which is nothing more than a self-invoked anonymous function executed in the context of the window:</p>
<pre>function(window){

    var a = 'foo', b = 'bar';

    function private(){
        // do something
    }

    window.Module = {

        public: function(){
            // do something 
        }
    };

})(this);</pre>
<p>This expression is most useful when attempting to preserve the global namespace as any variables declared within the function body will be local to the closure but will still live throughout runtime. This is a popular means of encapsulating source code for applications and frameworks, typically exposing a single global interface to interact with.</p>
<h3>Call and Apply（函数调用）</h3>
<p>These two simple methods, inherent to all functions, allow you to execute any function in any desired context. The <em>call</em> function requires the arguments to be listed explicitly while the <em>apply</em> function allows you to provide the arguments as an array:</p>
<pre>function user(first, last, age){
    // do something 
}
user.call(window, 'John', 'Doe', 30);
user.apply(window, ['John', 'Doe', 30]);</pre>
<p>The result of both calls is exactly the same, the user function is invoked in the context of the window and provided the same three arguments.</p>
<p>ECMAScript 5 (ES5) introduced the <em>Function.prototype.bind</em> method that is used for manipulating context. It returns a new function that is permanently bound to the first argument of <em>bind</em> regardless of how the function is being used. It works by using a closure that is responsible for redirecting the call in the appropriate context. See the following polyfill for unsupported browsers:</p>
<pre>if(!('bind' in Function.prototype)){
    Function.prototype.bind = function(){
        var fn = this, context = arguments[0], args = Array.prototype.slice.call(arguments, 1);
        return function(){
            return fn.apply(context, args);
        }
    }
}</pre>
<p>It is commonly used where context is frequently lost: object-orientation and event handling. This is necessary because the <em>addEventListener</em> method of a node will always execute the callback in the context of the node the event handler is bound to, which is the way it should be. However if your employing advanced object-oriented techniques and require your callback to be a method of an instance, you will be required to manually adjust the context. This is where bind comes in handy:</p>
<pre>function MyClass(){
    this.element = document.createElement('div');
    this.element.addEventListener('click', this.onClick.bind(this), false);
}

MyClass.prototype.onClick = function(e){
    // do something
};</pre>
<p>While reviewing the source of the <em>bind</em> function, you may have also noticed what appears to be a relatively simple line of code involving a method of an Array:</p>
<pre>Array.prototype.slice.call(arguments, 1);</pre>
<p>What is interesting to note here is that the <em>arguments</em> object is not actually an array at all, however it is often described as an array-like object much like a nodelist (anything returned by <em>document.getElementsByTagName()</em>). They contain a length property and indexed values but they are still not arrays, and subsequently don’t support any of the native methods of arrays such as slice and push. However, because of their similar behavior, the methods of Array can be adopted or hijacked, if you will, and executed in the context of an array-like object, as in the case above.</p>
<p>This technique of adopting another object’s methods also applies to object-orientation when emulating classical inheritance in JavaScript:</p>
<pre>MyClass.prototype.init = function(){
    // call the superclass init method in the context of the "MyClass" instance
    MySuperClass.prototype.init.apply(this, arguments);
}</pre>
<p>By invoking the method of the superclass (<em>MySuperClass</em>) in the context of an instance of a subclass (<em>MyClass</em>), we can mimic this powerful design pattern.</p>
<h2>Conclusion</h2>
<p>It is important to understand these concepts before you begin to approach advanced design patterns, as scope and context play a significant and fundamental role in modern JavaScript. Whether were talking about closures, object-orientation and inheritance, or various native implementations, context and scope play an important role in all of them. If your goal is to master the JavaScript language and better understand all it encompasses, then scope and context should be one of your starting points.</p>
<p>参考文章：<a href="http://flippinawesome.org/2013/08/26/understanding-scope-and-context-in-javascript/" target="_blank">http://flippinawesome.org/2013/08/26/understanding-scope-and-context-in-javascript/</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e8%a7%a3%e8%af%bbjavascript%e7%9a%84scope%e5%92%8ccontext/">解读JavaScript的Scope和Context</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/%e8%a7%a3%e8%af%bbjavascript%e7%9a%84scope%e5%92%8ccontext/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>网站SEO优化之CSS JS代码合并</title>
		<link>http://blog.zhourunsheng.com/2013/08/%e7%bd%91%e7%ab%99seo%e4%bc%98%e5%8c%96%e4%b9%8bcss-js%e4%bb%a3%e7%a0%81%e5%90%88%e5%b9%b6/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/%e7%bd%91%e7%ab%99seo%e4%bc%98%e5%8c%96%e4%b9%8bcss-js%e4%bb%a3%e7%a0%81%e5%90%88%e5%b9%b6/#comments</comments>
		<pubDate>Fri, 30 Aug 2013 03:34:48 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[Web设计]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[SEO]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1824</guid>
		<description><![CDATA[<p>在优化网站的过程中，其中一个规则就是尽量减少HTTP的请求数量，比如图片的合并，JS，CSS代码的合并等，本文 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e7%bd%91%e7%ab%99seo%e4%bc%98%e5%8c%96%e4%b9%8bcss-js%e4%bb%a3%e7%a0%81%e5%90%88%e5%b9%b6/">网站SEO优化之CSS JS代码合并</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>在优化网站的过程中，其中一个规则就是尽量减少HTTP的请求数量，比如图片的合并，JS，CSS代码的合并等，本文介绍了一款PHP的自动合并脚本，可以在网站中使用，只要将原来需要引用JS和CSS的地方，换成该脚本的函数调用，那么脚本就会自动将过个请求合并成一个请求，重而提高网站的打开速度。</p>
<h3>Combining Assets</h3>
<p>One of the basic tenets of making a web-page load quickly is reducing the number of HTTP requests that your page makes. One very common way of doing this, is assuring that all of your CSS files are combined into one request (ideally in the &lt;head&gt; tag), and all of your javscript files are combined into one request (ideally at the bottom of the &lt;body&gt; tag).<span id="more-1824"></span></p>
<h3>The Solution</h3>
<p>I’ve worked with several large sites which each had their own solutions, but recently I found myself needing to speed up a fairly simple site: <a title="Burndown Charts for Trello" href="https://burndownfortrello.com/">Burndown for Trello</a>.</p>
<p><strong>To make things simple (and so that I’d never have to write this again), I made a <tt>StaticResources</tt>system which will allow me to put just one PHP script into the root directory of the site, and use a few simple calls to add files to the header or footer of the page.</strong> The script requires no setup, installation, or configuration to run correctly. However, it has some optional advanced settings (which we’ll discuss at the end).</p>
<h3>Usage</h3>
<p>Usage is very simple. Just make a call to add files to the system, so that they’re included in the header or footer. Both local and external files are added with the same kind of call.<br />
StaticResources - how to add files<br />
PHP</p>
<pre>StaticResources::addHeaderFile("css/GoogleWebfont_Tauri_Latin.css");
StaticResources::addFooterFile("jquery-ui.js");
StaticResources::addFooterFile("http://code.jquery.com/ui/1.9.1/jquery-ui.js");</pre>
<p>After you’ve added the files, make sure that somewhere in your code you print the HTML which will contain all of the files that were added<br />
StaticResources - printing the script and style tags<br />
PHP</p>
<pre>// put this at the bottom of the HEAD tag
print StaticResources::getHeaderHtml();

// put this at the bottom of the BODY tag
print StaticResources::getFooterHtml();</pre>
<p>Here’s an example file of how you’d include a bunch of JS / CSS using this <tt>StaticResources</tt> system. It obviously won’t work if you don’t create JS/CSS files in the locations referenced, but this is very simple code to show the system in action.</p>
<p>StaticResources - EXAMPLE USAGE PAGE<br />
PHP</p>
<pre>&lt;?php

include 'static.php';

// You can add header files any time before the StaticResources::getHeaderHtml() call.
StaticResources::addHeaderFile("css/GoogleWebfont_Tauri_Latin.css");

// You can add footer files any time before the StaticResources::getHeaderHtml() call.
StaticResources::addFooterFile("jquery-1.9.1.js");

// For files that won't change (like a specific version of jQuery), it's often better to host it on your
// own server instead of making a separate HTTP request to the CDN.
StaticResources::addFooterFile("jquery-ui.js");

?&gt;&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&gt;
        &lt;title&gt;StaticResources example page&lt;/title&gt;
        &lt;?php

        // You can add header files any time before the StaticResources::getHeaderHtml() call.
        StaticResources::addHeaderFile("jquery-ui.min.css");

        // A good place to output the HTML is right at the end of the HEAD tag.
        // getHeaderHtml() returns a string (to be more flexible with various PHP frameworks)
        // so be sure to print its result.
        print StaticResources::getHeaderHtml();
        ?&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;header&gt;
            Created for &lt;a href='https://burndownfortrello.com'&gt;Burndown for Trello&lt;/a&gt;.
        &lt;/header&gt;

        &lt;article&gt;
            &lt;h1&gt;StaticResources example page&lt;/h1&gt;
            &lt;p&gt;This page is an example of loading static resources in combined files.&lt;/p&gt;
        &lt;/article&gt;

        &lt;footer&gt;
            © 2013 - Sean Colombo
        &lt;/footer&gt;
        &lt;?php

        // For files that won't be changing
        StaticResources::addFooterFile("stripeIntegration.js");

        // To add a file from another site, just make sure to use the full URL.
        // Since there is no file extension, we have to pass in the filetype as a second parameter.
        StaticResources::addFooterFile("https://www.google.com/jsapi", "js");

        // Output the footerHtml at the bottom of the page. It doesn't have to be in the FOOTER tag, it should
        // be done as far down on the page as possible (close to the end of the BODY tag).
        print StaticResources::getFooterHtml();

        ?&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre>
<h3>The Code</h3>
<p>Without further delay, this is the code of <tt>static.php</tt> that you should put in the main (eg: public_html) directory of your app.<br />
static.php - THE ACTUAL CODE<br />
PHP</p>
<pre>&lt;?php
/**
* @author Sean Colombo
* @date 20130817
*
* This script is designed to be a generalized (not site-specific) tool
* for including static JS/CSS assets in PHP web-apps.  It's a single file,
* requires no configuration to start using it, and makes delivery of static
* assets significantly faster.
*
* BEST PRACTICE:
* Ideally, any javascript that doesn't NEED to be loaded higher up in the page should be
* loaded in the footer (added using addFooterFile()) so that the page can render while the
* javascript is still being requested from the server.
* However, since the page cannot render properly until the CSS is loaded, any CSS used for the initial
* page-layout should be put into the header. Otherwise the page will start out looking unstyled, then
* will re-flow (flicker or adjust) after the CSS file is loaded.
*
* The performance benefits include:
* - Making less HTTP-requests results in a lot less setup/teardown and latency-time being wasted.
* - Good caching headers by default (makes subsequent page-requests faster since the user won't have to
*   make another request for the file they already have cached).
* - Minification: NOTE: NOT DONE YET - this will make the js/css files significantly smaller which decreases
*   the download-time.
*
* This script should be placed in the root directory of your site.  It is both an includable
* script (for building the list of files) and an endpoint (for actually serving up combined files).
*
* Remote files are currently sent as separate requests from local files - they are not bundled into
* the same package.  However, the implementation details are transparent - no special action needs to
* be taken for remote files. Just add them like you'd add a resource that's on your own server, and as
* long as you include the full URL, they will be handled properly.  Example:
*        // Local and remote files are included the same way.
*        StaticResources::addHeaderFile("js/myFile.js");
*        StaticResources::addHeaderFile("http://cdn.google.com/someOtherFile.js");
*
* == USAGE ==
* To output the header files - YOU MUST ADD THIS CALL. PUT IT RIGHT BEFORE THE END OF THE &lt;HEAD&gt; TAG.
*        print StaticResources::getHeaderHtml();
*
* To output the footer files - YOU MUST ADD THIS CALL. PUT IT RIGHT BEFORE THE END OF THE &lt;BODY&gt; TAG.
*        print StaticResources::getFooterHtml();
*
* To add files to the HEAD tag or to the bottom of the page:
*        StaticResources::addHeaderFile("css/GoogleWebfont_Tauri_Latin.css");
*        StaticResources::addFooterFile("jquery-ui.js");
*        StaticResources::addFooterFile("http://code.jquery.com/ui/1.9.1/jquery-ui.js");
*
* To add a file which doesn't have an extension (eg: ".js") at the end.
*        StaticResources::addFooterFile("https://www.google.com/jsapi", "js");
*
* Optional: to specify that this file should be served from a different URL (eg: a Content Delivery Network).
* Put this right below where you include() this static.php file:
*        StaticResources::setRootUrl("http://cdn.example.com/"); // use a trailing slash
*
*
*
* TODO: Add minification
* TODO: LATER: Add options to allow the files to use HTML5 async and deferred attributes.
*/

/**
* For security reasons, we limit the types of files that can be outputted.
* If this wasn't here, someone could request each of your .php files and read all of
* your source-code (and password files, etc.), so please only change this if you understand
* the implications.
*
* NOTE: See STATICRESOURCES_ALLOWED_FILE_TYPES below for the actual array.
*/

// Need to define this way (serialized and inside of define()) because arrays can't be class-constants.
define("STATICRESOURCES_ALLOWED_FILE_TYPES", serialize( array( "js", "css" ))); // all types must be lowercase!

// Character(s) used in the URL to separate file names. This should be a character that's not
// allowed in filenames, otherwise a real file could be split into two filenames.
// For example: "bob-vila.txt" would be interpreted as "bob" and "vila.txt" if the delimiter was "-".
// There is no perfect result for this (since different characters are allowed on different operating systems).
define("STATICRESOURCES_FILE_DELIMITER", "||");

// If this script is being run as the page (rather than just included) then serve up the files
// being requested.
if( $_SERVER['SCRIPT_FILENAME'] == __FILE__ ){
    $files = (isset($_GET['files']) ? $_GET['files'] : "");
    $files = explode(STATICRESOURCES_FILE_DELIMITER, $files); // turn it into an array.

    if(count($files) == 0){
        print "/* NOTE: No files found in the URL.\n";
        print "Please specify the file names in the 'file' parameter, separated by \"".STATICRESOURCES_FILE_DELIMITER."\"\n";
        print "See ".basename(__FILE__)." for documentation on how to use this system.\n";
        print "*/\n";
    } else {
        $fileType = ""; // don't know the fileType until we examine the file names.

        foreach($files as $fileName){
            $fileName = StaticResources::sanitizeFileName($fileName);

            $ext = StaticResources::getFileExtension($fileName);
            if( (!empty($fileType)) &amp;&amp; ($ext != $fileType) ){
                // Warn the user that they're mixing file-types.
                print "\n\n/* ===== WARNING: \"$fileName\" is of wrong file type. ";
                print "It is a '$ext' file, but this is a '$fileType' file. Skipped. ===== */\n\n\n\n";

                // Don't output this file since it's the wrong type. Skip to next file.
                continue;
            } else {
                // If fileType is still empty, this is the first file encountered.  Set correct headers.
                if(empty($fileType)){
                    $fileType = $ext;

                    // Set the content-type headers
                    $allowMimeSniffing = false; // usually, listen to our Content-types instead of sniffing Mime-type. Only exception is when we don't set a Content-type.
                    if($fileType == "css"){
                        header("Content-type: text/css");
                    } else if($fileType == "js"){
                        header("Content-type: application/javascript");
                    } else {
                        $allowMimeSniffing = true;
                        print "/* WARNING: Don't know what Content-type header to set for files with extension: '$fileType' */\n";
                    }
                    if(!$allowMimeSniffing){
                        // Disables IE mime-type sniffing so it always listens to Content-type.
                        header("X-Content-Type-Options: nosniff");
                    }

                    // Set reasonable caching headers.
                    // LATER: Could add a configuration array for tweaking the expiration by file-type. This is a reasonable default though.
                    $SECONDS_IN_MONTH = 60*60*24*30;
                    header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + $SECONDS_IN_MONTH)); // needs to be after session_start if session_start is used.
                }
            }

            // If there is a query-string, chop that off before looking for the file.
            if(strpos($fileName, "?") !== false){
                $fileName = substr($fileName, 0, strpos($fileName, "?"));
            }

            // Read, preprocess (eg: minify), and print the file contents.
            print "/* File: \"$fileName\" */\n";
            @$content = file_get_contents($fileName);
            if($content === false){
                print "/* FILE NOT FOUND */\n\n";
            } else {

                // TODO: PRE-PROCESS THE CONTENT.  MINIFICATION, ETC.
                // TODO: PRE-PROCESS THE CONTENT.  MINIFICATION, ETC.

                // Prints the content.
                print "$content\n\n";
            }
        }
    }
}

/**
* Helper class for creating tags which will import several static resources in a single
* request.
*
* The most common way to interface with this class is through the static functions at the
* bottom of the class (which will all operate on the same singleton).
*/
class StaticResources {
    const ERR_WRONG_FILETYPE = "Error in StaticResources: Tried to add a file with a file-type that isn't allowed.  File had extension '.%s' which is not in ALLOWED_FILE_TYPES in %s\n";
    const ERR_CANT_OUTPUT_FILETYPE = "Error in StaticResources: Don't know how to include code for files of type '%s'\n";
    const ERR_ALREADY_OUTPUT_HEADER = "Error in StaticResources: Tried to add a head file after the head files have already been retrieved. The file that was added late was: \"%s\".";
    const ERR_ALREADY_OUTPUT_FOOTER = "Error in StaticResources: Tried to add a footer file after the footer files have already been retrieved. The file that was added late was: \"%s\".";

    private static $ALLOWED_FILE_TYPES; // we have to initialize this in the constructor because arrays can't be class-constants.
    private $staticResourcesRootUrl = "./";

    // NOTE: Inclusion-order matters for both js and css, so never sort the array of fileNames... always output them in the order
    // received.
    private $_headerFilesByType = array('js' =&gt; array(), 'css' =&gt; array());
    private $_footerFilesByType = array('js' =&gt; array(), 'css' =&gt; array());

    private $_headerAlreadyReturned = false;
    private $_footerAlreadyReturned = false;

    /**
     * Basic constructor - note that most of the interaction with this class will
     * be done using static functions which grab a singleton.
     */
    public function __construct(){
        self::$ALLOWED_FILE_TYPES = unserialize( STATICRESOURCES_ALLOWED_FILE_TYPES );

        foreach(self::$ALLOWED_FILE_TYPES as $type){
            $this-&gt;_headerFilesByType[$type] = array();
            $this-&gt;_footerFilesByType[$type] = array();
        }
    } // end constructor

    /**
     * If your static resources are served from another domain (such as cdn.yoursite.com), then set
     * that here. This string is prepended to the filename (eg: "static.php") when building the URL for the combined-file.
     */
    public function mySetRootUrl($rootUrl){
        $this-&gt;staticResourcesRootUrl = $rootUrl;
    }

    /**
     * Adds the filename to the list of files to include in the head tag for its given
     * filetype.  If the fileName does not have an extension of one of the ALLOWED_FILE_TYPES,
     * then the file will NOT be added and a warning will be given instead.
     *
     * For these files to be used, the calling code must remember to print the results of
     * getHeaderHtml() in the &lt;head&gt; tag.
     *
     * NOTE: The fileName must be structured relative to where THIS (StaticResources) file is,
     * and should not be in normal URL format, but rather in a format that this file can be read
     * from disk.  So doing "/directory/filename.ext" to get to the root of public_html will not work
     * unless the StaticResources file happens to be in public_html.  If this sounds confusing,
     * the simplest fix is to put this StaticResources file in your public_html directory, then you
     * can write file names exactly the way you would have written them in &lt;script&gt;&lt;/script&gt; tags.
     *
     * @param fileType - if this is empty, the fileType will be detected from the filename. Sometimes
     * the filename might not have an extension (some JS libraries don't have file extensions) so you may
     * need to specify this value. Should be "js" or "css".
     */
    public function myAddHeaderFile($fileName, $fileType="", $warnIfAddedLate=true){
        // Make sure this file wasn't added after the header files were already displayed.
        if( $warnIfAddedLate &amp;&amp; $this-&gt;_headerAlreadyReturned){
            $errorString = sprintf(self::ERR_ALREADY_OUTPUT_HEADER, $fileName);
            trigger_error($errorString, E_USER_WARNING);
        }

        // If this file type is allowed, then remember it for later.
        if( $this-&gt;fileIsAllowed($fileName) ){
            $ext = (empty($fileType) ? StaticResources::getFileExtension($fileName) : strtolower($fileType));
            $this-&gt;headerFilesByType[ $ext ][] = $fileName;
        } else {
            $ext = (empty($fileType) ? StaticResources::getFileExtension($fileName) : strtolower($fileType));
            $errorString = sprintf(self::ERR_WRONG_FILETYPE, $ext, __FILE__);
            trigger_error($errorString, E_USER_WARNING);
        }
    } // end addHeaderFile()

    /**
     * Adds the filename to the list of files to include at the bottom of the BODY tag.
     * If the fileName does not have an extension of one of the ALLOWED_FILE_TYPES,
     * then the file will NOT be added and a warning will be given instead.
     *
     * For these files to be used, the calling code must remember to print the results of
     * getFooterHtml() somewhere very late in the document, ideally at the very bottom of
     * the &lt;body&gt; tag (right before &lt;/body&gt;).
     *
     * @param fileType - if this is empty, the fileType will be detected from the filename. Sometimes
     * the filename might not have an extension (some JS libraries don't have file extensions) so you may
     * need to specify this value. Should be "js" or "css".
     */
    public function myAddFooterFile($fileName, $fileType="", $warnIfAddedLate=true){
        // Make sure this file wasn't added after the footer files were already displayed.
        if( $warnIfAddedLate &amp;&amp; $this-&gt;_footerAlreadyReturned){
            $errorString = sprintf(self::ERR_ALREADY_OUTPUT_FOOTER, $fileName);
            trigger_error($errorString, E_USER_WARNING);
        }

        // If this file type is allowed, then remember it for later.
        if( $this-&gt;fileIsAllowed($fileName) ){
            $ext = (empty($fileType) ? StaticResources::getFileExtension($fileName) : strtolower($fileType));
            $this-&gt;footerFilesByType[ $ext ][] = $fileName;
        } else {
            $ext = (empty($fileType) ? StaticResources::getFileExtension($fileName) : strtolower($fileType));
            $errorString = sprintf(self::ERR_WRONG_FILETYPE, $ext, __FILE__);
            trigger_error($errorString, E_USER_WARNING);
        }
    } // end addFooterFile()

    /**
     * Returns a string which contains the HTML that's needed to
     * import the files in the HEAD tag.  This should be called exactly
     * once (and it's results should be printed in the &lt;head&gt; tag) on every
     * page.
     */
    public function myGetHeaderHtml(){
        $html = "&lt;!-- StaticResources::getHeaderHtml() --&gt;\n" . $this-&gt;getHtmlForArray( $this-&gt;headerFilesByType );
        $_headerAlreadyReturned = true;
        return $html;
    } // end getHeaderHtml()

    /**
     * Returns a string which contains the HTML that's needed to
     * import the files at the bottom of the BODY tag.  This should be called exactly
     * once (and it's results should be printed at the bottom of the &lt;body&gt; tag - right
     * before &lt;/body&gt;) on every page.
     */
    public function myGetFooterHtml(){
        $html = "&lt;!-- StaticResources::getFooterHtml() --&gt;\n" . $this-&gt;getHtmlForArray( $this-&gt;footerFilesByType );
        $_footerAlreadyReturned = true;
        return $html;
    } // end getFooterHtml()

    /**
     * Given an associative array of static resources whose keys are
     * resource types and whose values are arrays of fileNames (local
     * and/or remote), this will return a string which contains the
     * HTML that's needed to import those files.
     */
    private function getHtmlForArray($filesByType){
        $html = "";

        // The URL of this script (which will act as an endpoint and serve up the actual content.
        $url = $this-&gt;staticResourcesRootUrl . basename(__FILE__);
        foreach($filesByType as $fileType =&gt; $files){
            $localFiles = array();

            foreach($files as $fileName){
                if(StaticResources::isRemoteFile($fileName)){
                    // Add the HTML for including the remote file.
                    if($fileType == "css"){
                        $html .= "        &lt;link rel=\"stylesheet\" href=\"$fileName\"/&gt;\n";
                    } else if($fileType == "js"){
                        $html .= "        &lt;script src=\"$fileName\"&gt;&lt;/script&gt;\n";
                    } else {
                        // Each file type needs to be included a certain way, and we don't recognize this fileType.
                        $errorString = sprintf(self::ERR_CANT_OUTPUT_FILETYPE, $fileType);
                        trigger_error($errorString, E_USER_WARNING);
                    }
                } else {
                    $localFiles[] = $fileName;
                }
            }

            // Output the HTML which makes the request for the combined-file of all local files of the same fileType.
            if(count($localFiles) &gt; 0){

                // TODO: TWEAK SO THAT THE DELIMITER ISN'T URL-ENCODED. MAKES IT MORE READABLE AND SHORTER.
                // TODO: TWEAK SO THAT THE DELIMITER ISN'T URL-ENCODED. MAKES IT MORE READABLE AND SHORTER.

                $fullUrl = $url . "?files=".rawurlencode( implode(STATICRESOURCES_FILE_DELIMITER, $localFiles) );

                if($fileType == "css"){
                    $html .= "        &lt;link rel=\"stylesheet\" href=\"$fullUrl\"/&gt;\n";
                } else if($fileType == "js"){
                    $html .= "        &lt;script src=\"$fullUrl\"&gt;&lt;/script&gt;\n";
                } else {
                    // Each file type needs to be included a certain way, and we don't recognize this fileType.
                    $errorString = sprintf(self::ERR_CANT_OUTPUT_FILETYPE, $fileType);
                    trigger_error($errorString, E_USER_WARNING);
                }
            }
        }

        return $html;
    } // end getHtmlForArray()

    /**
     * Returns true if the given fileName is allowed to be included, false otherwise.
     * The reason a file may not be allowed is that it's of the wrong file-type. One
     * reason for this is that we don't want attackers to request file types that may
     * contain password-files or source code that some users of this script might not
     * want to make public, etc..
     */
    private function fileIsAllowed($fileName){
        $fileIsAllowed = true;
        if( !StaticResources::isRemoteFile($fileName)){
            $fileExtension = strtolower( StaticResources::getFileExtension($fileName) );
            $fileIsAllowed = in_array($fileExtension, self::$ALLOWED_FILE_TYPES);
        }
        return $fileIsAllowed;
    } // end fileIsAllowed()

    /**
     * Returns true if the fileName is from another site and false if it is from this site.
     */
    public static function isRemoteFile($fileName){
        // If it starts with a protocol (ftp://, http://, https://, etc.) then it is remote (not local).
        return (0 &lt;  preg_match("/^[a-z0-9]+:\/\//i", $fileName));
    }

    /**
     * If the 'fileName' is a local file, this will return that fileName in such a way
     * that the fileName can be loaded from disk safely (without allowing the user to
     * jump out of the current directory with "../" or absolute directories such
     * as "/usr/bin/").
     */
    public static function sanitizeFileName($fileName){
        // Only local files need to be sanitized.
        if( !StaticResources::isRemoteFile($fileName)){
            // Make sure the user can't get above the current directory using "../".
            while(strpos($fileName, "../") !== false){
                $fileName = str_replace("../", "", $fileName);
            }

            // Starting out with current directory avoids abusing absolute paths such as "/usr/bin"
            if(strpos($fileName, "./") !== 0){ // if path already starts with ./, don't duplicate it.
                if(strpos($fileName, "/") === 0){ // path already starts with "/", just turn it into "./".
                    $fileName = ".$fileName";
                } else {
                    $fileName = "./$fileName"; // all other paths that start 'normally' (not "./" or "/").
                }
            }
        }

        return $fileName;
    }

    public static function getFileExtension($fileName){
        // If there is a query-string, chop that off before looking for the extension.
        if(strpos($fileName, "?") !== false){
            $fileName = substr($fileName, 0, strpos($fileName, "?"));
        }

        return pathinfo($fileName, PATHINFO_EXTENSION);
    }

    // ----- STATIC HELPERS -----
    /**
     * Gets a singleton object of the StaticResources type to make it easy for the
     * script to use StaticResources throughout the web-app without passing the object
     * around.  This is probably the most common use-case.
     */
    public static function getSingleton(){
        global $staticResourcesSingleton;
        if(empty($staticResourcesSingleton)){
            $staticResourcesSingleton = new StaticResources();
        }
        return $staticResourcesSingleton;
    } // end getSingleton()

    public static function addHeaderFile($fileName, $fileType="", $warnIfLateAdded=true){
        $singleton = StaticResources::getSingleton();
        $singleton-&gt;myAddHeaderFile($fileName, $fileType, $warnIfLateAdded);
    }
    public static function addFooterFile($fileName, $fileType="", $warnIfLateAdded=true){
        $singleton = StaticResources::getSingleton();
        $singleton-&gt;myAddFooterFile($fileName, $fileType, $warnIfLateAdded);
    }
    public static function getHeaderHtml(){
        $singleton = StaticResources::getSingleton();
        return $singleton-&gt;myGetHeaderHtml();
    }
    public static function getFooterHtml(){
        $singleton = StaticResources::getSingleton();
        return $singleton-&gt;myGetFooterHtml();
    }
    public static function setRootUrl( $rootUrl ){
        $singleton = StaticResources::getSingleton();
        $singleton-&gt;mySetRootUrl($rootUrl);
    }

} // end class StaticResources</pre>
<h3>Using this script with a CDN</h3>
<p>One drawback of serving a combined file is that your server has to read in all of the files, then print them all. This makes the call slightly slower than it has to be. One solution is to use a <a title="Content Delivery Network - Wikipedia" href="http://en.wikipedia.org/wiki/Content_delivery_network">CDN</a> to cache the files on your site. This way, even if it takes your server a second or so to generate the file the first time, it can be served to the next user instantly, from the CDN.</p>
<p>For <a title="Burndown for Trello" href="https://burndownfortrello.com/">Burndown for Trello</a>, we use <a title="Fastly CDN" href="http://www.fastly.com/">Fastly</a> as our CDN (disclosure: my fiance works there, but we use it because it’s free for one backend and it’s crazy fast).</p>
<p>The code to make this work with a CDN is very simple: one extra line below the include call:</p>
<p>How to serve the static assets from a CDN<br />
PHP</p>
<pre>include 'static.php';
StaticResources::setRootUrl("http://b4t.global.ssl.fastly.net/");</pre>
<h3>Code minification</h3>
<p>Another common performance trick is to use a <a href="http://en.wikipedia.org/wiki/Minification_(programming)">minifier</a> to reduce file-size. This hasn’t been added to the script yet, but may come in the future. If you have a favorite minifier, I’ve conspicuously commented in the code where it would make sense to run the minifier.</p>
<p>Minification takes a little while to run, so it’s highly recommended that you get a CDN if you’re using minificaiton.</p>
<h3>The End</h3>
<p>Hope you found the script useful. If you use it on your site, please link to it in the comments!<br />
Thanks,</p>
<p>参考文章：</p>
<p>1. <a href="http://bluelinegamestudios.com/blog/posts/simple-single-file-php-script-for-combining-js-css-assets" target="_blank">http://bluelinegamestudios.com/blog/posts/simple-single-file-php-script-for-combining-js-css-assets</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e7%bd%91%e7%ab%99seo%e4%bc%98%e5%8c%96%e4%b9%8bcss-js%e4%bb%a3%e7%a0%81%e5%90%88%e5%b9%b6/">网站SEO优化之CSS JS代码合并</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/%e7%bd%91%e7%ab%99seo%e4%bc%98%e5%8c%96%e4%b9%8bcss-js%e4%bb%a3%e7%a0%81%e5%90%88%e5%b9%b6/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>一种更符合自然语意的PHP正则表达式</title>
		<link>http://blog.zhourunsheng.com/2013/08/%e4%b8%80%e7%a7%8d%e6%9b%b4%e7%ac%a6%e5%90%88%e8%87%aa%e7%84%b6%e8%af%ad%e6%84%8f%e7%9a%84php%e6%ad%a3%e5%88%99%e8%a1%a8%e8%be%be%e5%bc%8f/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/%e4%b8%80%e7%a7%8d%e6%9b%b4%e7%ac%a6%e5%90%88%e8%87%aa%e7%84%b6%e8%af%ad%e6%84%8f%e7%9a%84php%e6%ad%a3%e5%88%99%e8%a1%a8%e8%be%be%e5%bc%8f/#comments</comments>
		<pubDate>Thu, 15 Aug 2013 14:07:37 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[Web设计]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Regular Expressions]]></category>
		<category><![CDATA[正则表达式]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1782</guid>
		<description><![CDATA[<p>在编程的过程中，尤其是文本的处理，我们很多时候都需要正则表达式，比如，验证用户输入的Email是否格式正确，电 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e4%b8%80%e7%a7%8d%e6%9b%b4%e7%ac%a6%e5%90%88%e8%87%aa%e7%84%b6%e8%af%ad%e6%84%8f%e7%9a%84php%e6%ad%a3%e5%88%99%e8%a1%a8%e8%be%be%e5%bc%8f/">一种更符合自然语意的PHP正则表达式</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>在编程的过程中，尤其是文本的处理，我们很多时候都需要正则表达式，比如，验证用户输入的Email是否格式正确，电话号码是否符合规范，或者其他的自定义规则。对新手来说，熟记正则表达式的规则比较困难，那么有没有一种更符合自然语义的正则表达式呢，本文会给你答案。</p>
<p><img alt="verbalexp" src="http://www.codediesel.com/wp-content/uploads/2013/08/verbalexp.png" width="583" height="262" /></p>
<p>Most newbie (and some seasoned) programmers have difficultly constructing Regular Expressions. Many a times one needs to create a Regexp quickly to test a a particular piece of code. However, not being comfortable withe Regexps can be a problem. <a title="https://github.com/VerbalExpressions/PHPVerbalExpressions" href="https://github.com/VerbalExpressions/PHPVerbalExpressions" target="_blank">VerbalExpressions</a> is a PHP library that enables you to construct regular expressions using natural language like constructs. Think of it like a DSL for building Regexps.<span id="more-1782"></span><br />
verbalexpressions 已经实现了多个语言版本的库，可以从<a href="http://verbalexpressions.github.io/" target="_blank">这里</a>得到。</p>
<p>Below is a sample PHP code that constructs a regular expression which tests whether a given string is a valid url.</p>
<p>如下示例用来测试给定的一个string是否是有效的url地址。</p>
<pre>&lt;?php

include_once('VerbalExpressions.php');

$regex = new VerEx;

$regex  -&gt;startOfLine()
        -&gt;then("http")
        -&gt;maybe("s")
        -&gt;then("://")
        -&gt;maybe("www.")
        -&gt;anythingBut(" ")
        -&gt;endOfLine();

if($regex-&gt;test("http://www.codediesel.com"))
    echo "valid url";
else
    echo "invalid url";

?&gt;</pre>
<p>The main part of the code the DSL like interface that helps you build a Regexp.</p>
<pre>$regex  -&gt;startOfLine()
        -&gt;then("http")
        -&gt;maybe("s")
        -&gt;then("://")
        -&gt;maybe("www.")
        -&gt;anythingBut(" ")
        -&gt;endOfLine();</pre>
<p>If you want to see what regular expression the code has built, we can use the <strong>getRegex</strong> function of the class.</p>
<pre>&lt;?php

include_once('VerbalExpressions.php');

$regex = new VerEx;

$regex  -&gt;startOfLine()
        -&gt;then("http")
        -&gt;maybe("s")
        -&gt;then("://")
        -&gt;maybe("www.")
        -&gt;anythingBut(" ")
        -&gt;endOfLine();

echo $regex-&gt;getRegex();</pre>
<p>This will print the Regexp given below.</p>
<p>打印输出正则表达式。</p>
<pre>/^(http)(s)?(\:\/\/)(www\.)?([^ ]*)$/m</pre>
<p>We can now use the above Regexp in our code to accomplish the same thing as above.</p>
<pre>$myRegexp = '/^(http)(s)?(\:\/\/)(www\.)?([^ ]*)$/m';

if (preg_match($myRegexp, 'http://www.codediesel.com')) {
    echo 'valid url';
} else {
    echo 'invalud url';
}</pre>
<p>We can also use the <strong>$regex</strong> object given in the above example directly in our code where the particular Regexp is required.</p>
<p>可以使用函数 <strong>getRegex</strong> 获取正则表达式规则，或者直接可以用 <strong>$regex</strong> 对象，库内部已经实现了转换。</p>
<p>内部的转换函数：</p>
<pre>/**
* Object to string
*
* PHP Magic method to return a string representation of the object.
*
* @access public
* @return string
*/
    public function __toString()
    {
        return $this-&gt;getRegex();
    }</pre>
<pre>include_once('VerbalExpressions.php');

$regex = new VerEx;

$regex  -&gt;startOfLine()
        -&gt;then("http")
        -&gt;maybe("s")
        -&gt;then("://")
        -&gt;maybe("www.")
        -&gt;anythingBut(" ")
        -&gt;endOfLine();

if (preg_match($regex, 'http://www.codediesel.com')) {
    echo 'valid url';
} else {
    echo 'invalud url';
}</pre>
<p>The PHP version of VerbalExpressions is a port of the original JavaScript version,<a title="jsverbalexpressions" href="https://github.com/VerbalExpressions/JSVerbalExpressions" target="_blank">JSVerbalExpressions</a>. Additional modifiers that will help you build regular expressions can be found <a title="verbalexpressions wiki" href="https://github.com/VerbalExpressions/JSVerbalExpressions/wiki" target="_blank">here</a>.</p>
<p>文章节选：<a href="http://www.codediesel.com/php/constructing-hard-regular-expressions-with-verbalexpressions/" target="_blank">constructing-hard-regular-expressions-with-verbalexpressions</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e4%b8%80%e7%a7%8d%e6%9b%b4%e7%ac%a6%e5%90%88%e8%87%aa%e7%84%b6%e8%af%ad%e6%84%8f%e7%9a%84php%e6%ad%a3%e5%88%99%e8%a1%a8%e8%be%be%e5%bc%8f/">一种更符合自然语意的PHP正则表达式</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/%e4%b8%80%e7%a7%8d%e6%9b%b4%e7%ac%a6%e5%90%88%e8%87%aa%e7%84%b6%e8%af%ad%e6%84%8f%e7%9a%84php%e6%ad%a3%e5%88%99%e8%a1%a8%e8%be%be%e5%bc%8f/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP中Session的使用</title>
		<link>http://blog.zhourunsheng.com/2013/08/php%e4%b8%adsession%e7%9a%84%e4%bd%bf%e7%94%a8/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/php%e4%b8%adsession%e7%9a%84%e4%bd%bf%e7%94%a8/#comments</comments>
		<pubDate>Thu, 15 Aug 2013 13:38:34 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[Web设计]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1778</guid>
		<description><![CDATA[<p>php中使用session的场合主要是用户登录信息的记录和页面之间数据的传递，session的有效期为浏览器的 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/php%e4%b8%adsession%e7%9a%84%e4%bd%bf%e7%94%a8/">PHP中Session的使用</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>php中使用session的场合主要是用户登录信息的记录和页面之间数据的传递，session的有效期为浏览器的生存期，直到用户关闭浏览器，服务器中的session数据才清除。本文介绍了怎么样利用session来存储变量。</p>
<p>In this article I’m going to show you some simple things to work with sessions in php, these are mostly used to store information about your users, like usernames, choices selected by users and similar, these however are stored on the server and deleted once the user closes the browser. To interact with session variables you need to carefully create them and then check them and accordingly do something based on their value. This is actually the main topic of this article, how to check and create php session variables.<span id="more-1778"></span></p>
<h2>Starting sessions …【启用session】</h2>
<p>It is imperative that you first and foremost start the session on your website, otherwise the session variables will be lost as soon as the page is refreshed or goes to a different page. This can be easily done using the <strong>session_start()</strong> php function.</p>
<pre>&lt;?php session_start(); ?&gt;

&lt;html&gt;
    &lt;head&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;p&gt;Some content here&lt;/p&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre>
<p>It is important that you use the <strong>session_start()</strong> function before any other code on your website.</p>
<h2>Creating php session variables【创建session变量】</h2>
<p>As you can see bellow, working wish session is similar to associative php arrays, the variable however needs to be <strong>$_SESSION</strong>. Then you create a session variable (more like an array element) by assigning it a value:</p>
<pre>&lt;?php
    session_start();
    $_SESSION['username']='Teddy';
    $_SESSION['logged']='yes';
?&gt;</pre>
<p>The above code will store the session variable ‘logged’ with the value ‘yes’ and based on this, you can probably show some specific links for a section that’s meant for logged users. Will show you in a second bellow.</p>
<h2>How to check session variable【访问session变量】</h2>
<p>Checking the session variable is important, else you’re just storing them for nothing, you need to make use of them in certain ways. Bellow its an example of using the above ‘logged’ session variable:</p>
<pre>&lt;?php
    session_start();
    $username = $_SESSION['username'];
    if(isset($_SESSION['logged']) &amp;&amp; $_SESSION['logged']=='yes') {
        echo "&lt;p&gt;&lt;a href='account-settings.php?user=$username' title='Account settings'&gt;Account settings&lt;/a&gt;&lt;/br&gt;";
        echo "&lt;p&gt;&lt;a href='view-profile.php' title='View profile&gt;View profile&lt;/a&gt;&lt;/br&gt;";
        echo "&lt;p&gt;&lt;a href='other-page.php' title='Other page'&gt;Some other page&lt;/a&gt;&lt;/br&gt;";
    }
// session is set, this will show similar as bellow:
// Account settings
// Edit account
// Some other page
?&gt;</pre>
<h2>Printing session variable【打印输出session变量】</h2>
<p>You can also print the session value wherever you need its value, for example:</p>
<pre>&lt;?php
    session_start();

    $username = $_SESSION['username'];
    if(isset($_SESSION['logged']) &amp;&amp; $_SESSION['logged']=='yes') {
        echo "Is $username logged in?&lt;br/&gt;";
        echo "Answer is: " . (isset($_SESSION['logged']) &amp;&amp; $_SESSION['logged']=='yes' ? $_SESSION['logged'] : "no");
    }
// Is Teddy logged in?
// Answer is: yes
?&gt;</pre>
<blockquote><p><strong>Heads up!</strong> If you are testing this, do not forget that sessions are being stored until you close your browser or until you clear your browser cookies and cache!</p></blockquote>
<p>As you can see, this is not exactly hard, but needs a bit of attention. Sessions are normally used for logged section, where you rely on session to show account level section for your users.</p>
<p>Do not forget that this is only an example to see how session work, you will need to secure these if you really wish to use them in production … but that’s another article that may come soon.</p>
<p>That’s it for now, don’t forget to share and follow our website for new articles!</p>
<p>文章节选：<a href="http://www.yourhowto.net/how-to-check-and-create-php-session-variables/" target="_blank">how-to-check-and-create-php-session-variables</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/php%e4%b8%adsession%e7%9a%84%e4%bd%bf%e7%94%a8/">PHP中Session的使用</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/php%e4%b8%adsession%e7%9a%84%e4%bd%bf%e7%94%a8/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>怎样写出更有效率的CSS</title>
		<link>http://blog.zhourunsheng.com/2013/08/%e6%80%8e%e6%a0%b7%e5%86%99%e5%87%ba%e6%9b%b4%e6%9c%89%e6%95%88%e7%8e%87%e7%9a%84css/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/%e6%80%8e%e6%a0%b7%e5%86%99%e5%87%ba%e6%9b%b4%e6%9c%89%e6%95%88%e7%8e%87%e7%9a%84css/#comments</comments>
		<pubDate>Thu, 15 Aug 2013 01:18:22 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[Web设计]]></category>
		<category><![CDATA[CSS]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1760</guid>
		<description><![CDATA[<p>在页面设计的过程中，总会使用CSS来进行样式设计，样式的选择器也无非常用4种，ID，Class，Tag和Uni [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e6%80%8e%e6%a0%b7%e5%86%99%e5%87%ba%e6%9b%b4%e6%9c%89%e6%95%88%e7%8e%87%e7%9a%84css/">怎样写出更有效率的CSS</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>在页面设计的过程中，总会使用CSS来进行样式设计，样式的选择器也无非常用4种，ID，Class，Tag和Universal，优先级从高到底，那么怎么样设计选择器和样式，可以提高CSS的解析效率，平时的设计中又会出现哪些误区，那么如下的文章值得一看。</p>
<p>Writing good CSS code can speed up page rendering. Essentially, the fewer rules the engine has to evaluate the better. <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Writing_efficient_CSS">MDN</a> groups CSS selectors in four primary categories, and these actually follow the order of how efficient they are:</p>
<ul>
<li>ID Rules</li>
<li>Class Rules</li>
<li>Tag Rules</li>
<li>Universal Rules</li>
</ul>
<p>The efficiency is generally quote from Even Faster Websites by Steve Souders which was published in 2009. Souders list is more detailed, though, and you can find the <a href="http://csswizardry.com/2011/09/writing-efficient-css-selectors/">full list referenced here</a>. You can find more details in <a href="https://developers.google.com/speed/docs/best-practices/rendering#UseEfficientCSSSelectors">Google’s best practices for efficient CSS selectors</a>.</p>
<p>In this article I wanted to share some simple examples and guidelines that I use for writing efficient and performant CSS. This is inspired by, and follows a similar format to, <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Writing_efficient_CSS">MDN’s guide to writing efficient CSS</a>.<span id="more-1760"></span></p>
<h2>Don’t Overqualify[过多的修饰符]</h2>
<p>As a general rule, don’t supply more information than is necessary.</p>
<pre>// bad
ul#someid {..}
.menu#otherid{..}

// good
#someid {..}
#otherid {..}</pre>
<h2>Descendant Selectors are the Worst[少用后代选择器]</h2>
<p>Not only is this not performant but it is fragile, as changes to the HTML can easily break your CSS.</p>
<pre>// very bad
html div tr td {..}</pre>
<h2>Avoid Chaining[少用链式]</h2>
<p>This is similar to overqualifying and it is preferable to simply create a new CSS class selector.</p>
<pre>// bad
.menu.left.icon {..}

// good
.menu-left-icon {..}</pre>
<h2>Stay KISS[简洁]</h2>
<p>Let’s imagine we have a DOM like this:</p>
<pre>&lt;ul id="navigator"&gt;
    &lt;li&gt;&lt;a href="#"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="#"&gt;Facebook&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="#"&gt;Dribbble&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</pre>
<p>Following upon the prior rules…</p>
<pre>// bad
#navigator li a {..}

// good
#navigator {..}</pre>
<h2>Use a Compact Syntax[采用缩写]</h2>
<p>Whenever possible, use the shorthand syntax.</p>
<pre>// bad
.someclass {
 padding-top: 20px;
 padding-bottom: 20px;
 padding-left: 10px;
 padding-right: 10px;
 background: #000;
 background-image: url(../imgs/carrot.png);
 background-position: bottom;
 background-repeat: repeat-x;
}

// good
.someclass {
 padding: 20px 10px 20px 10px;
 background: #000 url(../imgs/carrot.png) repeat-x bottom;
}</pre>
<h2>Avoid Needless Namespacing[多余的命名空间]</h2>
<pre>// bad
.someclass table tr.otherclass td.somerule {..}

//good
.someclass .otherclass td.somerule {..}</pre>
<h2>Avoid Needless Duplication[规则重复]</h2>
<p>Whenever you can, combine duplicate rules.</p>
<pre>// bad

.someclass {
 color: red;
 background: blue;
 font-size: 15px;
}

.otherclass {
 color: red;
 background: blue;
 font-size: 15px;
}

// good

.someclass, .otherclass {
 color: red;
 background: blue;
 font-size: 15px;
}</pre>
<h2>Condense Rules When You Can[继承压缩]</h2>
<p>Following on the prior rule, you can combine duplicate rules but still differentiate classes.</p>
<pre>// bad
.someclass {
 color: red;
 background: blue;
 height: 150px;
 width: 150px;
 font-size: 16px;
}

.otherclass {
 color: red;
 background: blue;
 height: 150px;
 width: 150px;
 font-size: 8px;
}

// good
.someclass, .otherclass {
 color: red;
 background: blue;
 height: 150px;
 width: 150px;
}

.someclass {
 font-size: 16px;
}

.otherclass {
 font-size: 8px;
}</pre>
<h2>Avoid Unclear Naming Conventions[命名规范]</h2>
<p>It is preferable to use semantic names. A good CSS class name should describe what it is about.</p>
<h2>Avoid !importants[避免!importants标签的使用]</h2>
<p>When possible, you should instead use good qualified selectors.</p>
<h2>Follow a Standard Declaration Order[标准顺序]</h2>
<p>While there are <a href="http://css-tricks.com/new-poll-how-order-css-properties/">a number of common ways</a> to order CSS properties, this is a commonly used one that I follow.</p>
<pre>.someclass {
 /* Positioning */
 /* Display &amp; Box Model */
 /* Background and typography styles */
 /* Transitions */
 /* Other */
}</pre>
<h2>Format Your Code Properly[格式化]</h2>
<p>Code that is easier to read is easier to maintain. Here’s the format I follow:</p>
<pre>// bad
.someclass-a, .someclass-b, .someclass-c, .someclass-d {
 ...
}

// good
.someclass-a, 
.someclass-b, 
.someclass-c, 
.someclass-d {
 ...
}

// good practice
.someclass {
    background-image:
        linear-gradient(#000, #ccc),
        linear-gradient(#ccc, #ddd);
    box-shadow:
        2px 2px 2px #000,
        1px 4px 1px 1px #ddd inset;
}</pre>
<h2>Where to Go From Here</h2>
<p>Obviously these are just a handful of rules that I try to follow in my own CSS to make it both more efficient and easier to maintain. If you want to read more on the topic, I suggest reading <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Writing_efficient_CSS">Writing Efficient CSS</a> on MDN and Google’s guide to <a href="https://developers.google.com/speed/docs/best-practices/rendering#UseEfficientCSSSelectors">optimize browser rendering</a>.</p>
<p>文章节选：<a href="http://blog.mathewdesign.com/2013/07/04/writing-performant-and-quality-css/">writing performant and quality css</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e6%80%8e%e6%a0%b7%e5%86%99%e5%87%ba%e6%9b%b4%e6%9c%89%e6%95%88%e7%8e%87%e7%9a%84css/">怎样写出更有效率的CSS</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/%e6%80%8e%e6%a0%b7%e5%86%99%e5%87%ba%e6%9b%b4%e6%9c%89%e6%95%88%e7%8e%87%e7%9a%84css/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>你需要了解的SQL注入技术</title>
		<link>http://blog.zhourunsheng.com/2013/08/%e4%bd%a0%e9%9c%80%e8%a6%81%e4%ba%86%e8%a7%a3%e7%9a%84sql%e6%b3%a8%e5%85%a5%e6%8a%80%e6%9c%af/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/%e4%bd%a0%e9%9c%80%e8%a6%81%e4%ba%86%e8%a7%a3%e7%9a%84sql%e6%b3%a8%e5%85%a5%e6%8a%80%e6%9c%af/#comments</comments>
		<pubDate>Sat, 03 Aug 2013 02:43:47 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[Web设计]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SQL Injection]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1706</guid>
		<description><![CDATA[<p>开发WEB的同学都应该听说过SQL注入，如果开发过程中，直接利用parameter中传入的值来进行数据库查询的 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e4%bd%a0%e9%9c%80%e8%a6%81%e4%ba%86%e8%a7%a3%e7%9a%84sql%e6%b3%a8%e5%85%a5%e6%8a%80%e6%9c%af/">你需要了解的SQL注入技术</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>开发WEB的同学都应该听说过SQL注入，如果开发过程中，直接利用parameter中传入的值来进行数据库查询的话就有SQL注入的风险，那么到底有哪些高级的SQL注入手法，相信读过本文，了解的就不是一点点了，看看黑客们是怎样注入的，那么，自己在以后的开发过程中也要适时留意，防范这种风险。<span id="more-1706"></span></p>
<div>
<p>Put on your black hats folks, it’s time to learn some genuinely interesting things about SQL injection. Now remember – y’all play nice with the bits and pieces you’re about to read, ok?</p>
<p>SQL injection is a particularly interesting risk for a few different reasons:</p>
<ol>
<li>It’s getting increasingly harder to write vulnerable code due to frameworks that automatically parameterise inputs – yet we still write bad code.</li>
<li>You’re not necessarily in the clear just because you use stored procedures or a shiny ORM (you’re aware that<a href="http://www.troyhunt.com/2012/12/stored-procedures-and-orms-wont-save.html">SQLi can still get through these</a>, right?) – we still build vulnerable apps around these mitigations.</li>
<li>It’s easily detected remotely by automated tools which can be orchestrated to crawl the web searching for vulnerable sites – yet we’re still putting them out there.</li>
</ol>
<p>It remains <a href="http://www.troyhunt.com/2010/05/owasp-top-10-for-net-developers-part-1.html">number one on the OWASP Top 10</a> for a very good reason – it’s common, it’s very easy to exploit and the impact of doing so is severe. One little injection risk in one little feature is often all it takes to disclose every piece of data in the whole system – and I’m going to show you how to do this yourself using a raft of different techniques.</p>
<p>I demonstrated how to protect against SQLi a couple of years back when I wrote about <a href="http://www.troyhunt.com/2010/05/owasp-top-10-for-net-developers-part-1.html">the OWASP Top 10 for .NET developers</a> so I’m not going to focus on mitigation here, this is all about exploiting. But enough of the boring defending stuff, let’s go break things!</p>
<p><a name="more"></a></p>
<h4>All your datas are belong to us (if we can break into the query context)</h4>
<p>Let’s do a quick recap on what it is that makes SQLi possible. In a nutshell, it’s about breaking out of the <em>data</em>context and entering the <em>query</em> context. Let me visualise this for you; say you have URL that includes a query string parameter such as “id=1” and that parameter makes its way down into a SQL query such as this:</p>
<p align="left"><img title="" alt="SELECT * FROM Widget WHERE ID = 1" src="http://lh3.ggpht.com/-v8wf12yBReA/Ufd7GKiDAwI/AAAAAAAAFik/h1aZURZ2j7Q/Untitled-12.png?imgmax=800" width="632" height="103" border="0" /></p>
<p>The entire URL probably looked something like this:</p>
<p><img title="" alt="http://widgetshop.com/Widget/?id=1" src="http://lh3.ggpht.com/-yJGNQFXQ8Jg/Ufd7Gs6hNPI/AAAAAAAAFis/EMvlU6I2FuQ/Untitled-22.png?imgmax=800" width="650" height="110" border="0" /></p>
<p>Pretty basic stuff, where it starts to get interesting is when you can manipulate the data in the URL such that it changes the value passed to the query. Ok, changing “1” to “2” will give you a different widget and that’s to be expected, but what if you did this:</p>
<p><span style="text-decoration: underline;"><span style="color: #0000ff;">http://widgetshop.com/widget/?id=1 or 1=1</span></span></p>
<p>That might then persist through to the database server like so:</p>
<pre>SELECT * FROM Widget WHERE ID = 1 OR 1=1</pre>
<p>What this tells us is that the data is not being sanitised – in the examples above the ID should clearly be an integer yet the value “1 OR 1=1” has been accepted. More importantly though, because this data has simply been appended to the query <em>it has been able to change the function of the statement</em>. Rather than just selecting a single record, this query will now select <em>all</em> records as the “1=1” statement will always be true. Alternatively, we could force the page to return <em>no</em> records by changing “or 1=1” to “and 1=2” as it will always be false hence no results. Between these two alternatives we can easily assess if the app is at risk of an injection attack.</p>
<p>This is the essence of SQL injection – manipulating query execution with untrusted data – and it happens when developers do things like this:</p>
<p>query = "SELECT * FROM Widget WHERE ID = "+ Request.QueryString["ID"];<br />
// Execute the query...</p>
<p>Of course what they should be doing is parameterising the untrusted data but I’m not going to go into that here (refer back to <a href="http://www.troyhunt.com/2010/05/owasp-top-10-for-net-developers-part-1.html">part one of my OWASP series</a> for more info on mitigation), instead I want to talk more about exploiting SQLi.</p>
<p>Ok, so that background covers how to demonstrate that a risk is present, but what can you now do with it? Let’s start exploring some common injection patterns.</p>
<h4>Joining the dots: Union query-based injection</h4>
<p>Let’s take an example where we expect a set of records to be returned to the page, in this case it’s a list of widgets of “TypeId” 1 on a URL like this:</p>
<p><span style="color: #0000ff;"><span style="text-decoration: underline;">http://widgetshop.com/Widgets/?TypeId=1</span></span></p>
<p>The result on the page then looks like so:</p>
<p><img title="" alt="3 widgets returned to the page" src="http://lh3.ggpht.com/-g7i0u59D7lE/Ufd7HFnj82I/AAAAAAAAFi0/GcgJwfnJYIQ/image6.png?imgmax=800" width="68" height="117" border="0" /></p>
<p>We’d expect that query to look something like this once it hits the database:</p>
<pre>SELECT Name FROM Widget WHERE TypeId = 1</pre>
<p>But if we can apply what I’ve outlined above, namely that we might be able to just append SQL to the data in the query string, we might be able to do something like this:</p>
<p><span style="text-decoration: underline;"><span style="color: #0000ff;">http://widgetshop.com/Widgets/?TypeId=1 union all select name from sysobjects where xtype='u'</span></span></p>
<p>Which would then create a SQL query like so:</p>
<pre>SELECT Name FROM Widget WHERE TypeId = 1 union all select name from sysobjects where xtype='u'</pre>
<p>Now keep in mind that the sysobjects table is the one that lists all the objects in the database and in this case we’re filtering that list by xtype “u” or in other words, user tables. When an injection risk is present that would mean the following output:</p>
<p><img title="" alt="3 widgets returned to the page followed by 2 internal table names" src="http://lh5.ggpht.com/-A6YoNxkRaqc/Ufd7HrZDOXI/AAAAAAAAFi8/GfPVE2fE21E/image9.png?imgmax=800" width="66" height="187" border="0" /></p>
<p>This is what’s referred to as a union query-based injection attack as we’ve simply appended an additional result set to the original and its made its way out directly into the HTML output – easy! Now that we know there’s a table called “User” we could do something like this:</p>
<p><span style="text-decoration: underline;"><span style="color: #0000ff;">http://widgetshop.com/Widgets/?TypeId=1 union all select password from [user]</span></span></p>
<p>SQL Server gets a bit uppity if the table name of “user” is not enclosed in square brackets given the word has other meanings in the DB sense. Regardless, here’s what that gives us:</p>
<p><img title="" alt="3 widgets returned to the page followed by a password" src="http://lh6.ggpht.com/-vJk-tgqAQpg/Ufd7IFPK7lI/AAAAAAAAFjE/uhgNNX-YgWY/image2.png?imgmax=800" width="89" height="148" border="0" /></p>
<p>Of course the UNION ALL statement only works when the first SELECT statement has the same number of columns as the second. That’s easily discoverable though, you just try going with a bit of ”union all select ‘a’” then if that fails “union all select ‘a’, ‘b’” and so on. Basically you’re just guessing the number of columns until things work.</p>
<p>We could go on and on down this path and pull back all sorts of other data, let’s move on to the next attack though. There are times when a union-based attack isn’t going to play ball either due to sanitisation of the input or how the data is appended to the query or even how the result set is displayed to the page. To get around that we’re going to need to get a bit more creative.</p>
<h4>Making the app squeal: Error-based injection</h4>
<p>Let’s try another pattern – what if we did this:</p>
<p><span style="text-decoration: underline;"><span style="color: #0000ff;">http://widgetshop.com/widget/?id=1 or x=1</span></span></p>
<p>Hang on, that’s not valid SQL syntax, the “x=1” piece won’t compute, at least not unless there’s a column called “x” so won’t it just throw an exception? Precisely, in fact it means you’ll see an exception like this:</p>
<p><img title="" alt="Invalid column name 'x'" src="http://lh6.ggpht.com/-iSzrFMeO-dw/Ufd7InmwlzI/AAAAAAAAFjM/u-o_hoEoAuM/image14.png?imgmax=800" width="845" height="187" border="0" /></p>
<p>This an ASP.NET error and other frameworks have similar paradigms but the important thing is that the error message is disclosing information about the internal implementation, namely that there is no column called “x”. Why is this important? It’s fundamentally important because once you establish that an app is leaking SQL exceptions, you can do things like this:</p>
<p><span style="text-decoration: underline;"><span style="color: #0000ff;">http://widgetshop.com/widget/?id=convert(int,(select top 1 name from sysobjects where id=(select top 1 id from (select top 1 id from sysobjects where xtype='u' order by id) sq order by id DESC)))</span></span></p>
<p>That’s a lot to absorb and I’ll come back to it in more detail, the important thing is though that it will yield this result in the browser:</p>
<p><img title="" alt="Conversion failed when converting the varchar value 'Widget' to data type int." src="http://lh3.ggpht.com/-mM4agaXM6Cg/Ufd7JQvO_fI/AAAAAAAAFjU/tmIarbT7t_w/image17.png?imgmax=800" width="844" height="184" border="0" /></p>
<p>And there we have it, we’ve now discovered that there is a table in the database called “Widget”. You’ll often see this referred to as “Error-based SQL injection” due to the dependency on internal errors. Let’s deconstruct the query from the URL:</p>
<pre>convert(int, (
    select top 1 name from sysobjects where id=(
      select top 1 id from (
        select top 1 id from sysobjects where xtype='u' order by id
      ) sq order by id DESC
    )
  )
)</pre>
<p>Working from the deepest nesting up, get the first record ID from the sysobjects table after ordering by ID. From that collection, get the <em>last</em> ID (this is why it orders in descending) and pass that into the top select statement. That top statement is then only going to take the table name <em>and try to convert it to an integer</em>. The conversion to integer will almost certainly fail (please people, don’t name your tables “1” or “2” or any other integer for that matter!) and that exception then discloses the table name in the UI.</p>
<p>Why three select statements? Because it means we can go into that innermost one and change “top 1” to “top 2” which then gives us this result:</p>
<p><img title="" alt="Conversion failed when converting the varchar value 'User' to data type int." src="http://lh4.ggpht.com/-GJZJi34IK1Q/Ufd7J5OtHmI/AAAAAAAAFjc/XnioL8T3rvo/image20.png?imgmax=800" width="844" height="185" border="0" /></p>
<p>Now we know that there’s a table called “User” in the database. Using this approach we can discover all the column names of each table (just apply the same logic to the syscolumns table). We can then extend that logic even further to select data from table columns:</p>
<p><img title="" alt="Conversion failed when converting the varchar value 'P@ssw0rd' to data type int." src="http://lh5.ggpht.com/-QDCJLdU038g/Ufd7KXKELeI/AAAAAAAAFjk/XwdEg1W2yyI/image26.png?imgmax=800" width="844" height="184" border="0" /></p>
<p>In the screen above, I’d already been able to discover that there was a table called “User” and a column called “Password”, all I needed to do was select out of that table (and again, you can enumerate through all records one by one with nested select statements), and cause an exception by attempting to convert the string to an int (you can always append an alpha char to the data if it <em>really is</em> an int then attempt to convert the whole lot to an int which will cause an exception). If you want to get a sense of just how easy this can be, I recorded a <a href="http://www.troyhunt.com/2012/10/hacking-is-childs-play-sql-injection.html">little video last year where I teach my 3 year old to automate this with Havij</a> which uses the technique.</p>
<p>But there’s a problem with all this – it was only possible because the app was a bit naughty and exposed internal error messages to the general public. In fact the app quite literally <em>told us</em> the names of the tables and columns and then disclosed the data when we asked the right questions, but what happens when it doesn’t? I mean what happens when the app is correctly configured so as not to leak the details of internal exceptions?</p>
<p>This is where we get into “blind” SQL injection which is the genuinely interesting stuff.</p>
<h4>Hacking blind</h4>
<p>In the examples above (and indeed in many precedents of successful injection attacks), the attacks are dependent on the vulnerable app explicitly disclosing internal details either by joining tables and returning the data to the UI or by raising exceptions that bubble up to the browser. Leaking of internal implementations is always a bad thing and as you saw earlier, security misconfigurations such as this can be leveraged to disclose more than just the application structure, you can actually pull <em>data</em> out through this channel as well.</p>
<p>A correctly configured app <em>should</em> return a message more akin to this one here when an <em>unhandled</em> exception occurs:</p>
<p><img title="" alt="Error. An error occurred while processing your request." src="http://lh5.ggpht.com/-IN_JPtT0ZVc/Ufd7Ky6aXZI/AAAAAAAAFjs/cKXM5z4S7SQ/image29.png?imgmax=800" width="845" height="255" border="0" /></p>
<p>This is the default error page from a brand new ASP.NET app with custom errors configured but again, similar paradigms exist in other technology stacks. Now this page is exactly the same as the earlier ones that showed the internal SQL exceptions but rather than letting them bubble up to the UI they’re being hidden and a friendly error message shown instead. Assuming we also couldn’t exploit a union-based attack, the SQLi risk is entirely gone, right? Not quite…</p>
<p>Blind SQLi relies on us getting a lot more <em>implicit</em> or in other words, drawing our conclusions based on other observations we can make about the behaviour of the app that aren’t quite as direct as telling us table names or showing column data directly in the browser by way of unions or unhandled exceptions. Of course this now begs the question – how can we make the app behave in an observable fashion such that it discloses the information we had earlier without explicitly telling us?</p>
<p>We’re going to look at two approaches here: boolean-based and time-based.</p>
<h4>Ask, and you shall be told: Boolean-based injection</h4>
<p>This all comes down to asking the right questions of the app. Earlier on, we could explicitly ask questions such as “What tables do you have” or “What columns do you have in each table” and the database would explicitly tell us. Now we need to ask a little bit differently, for example like this:</p>
<p><span style="text-decoration: underline;"><span style="color: #0000ff;">http://widgetshop.com/widget/?id=1 and 1=2</span></span></p>
<p>Clearly this equivalency test can never be true – one will never be equal to two. How an app at risk of injection responds to this request is the cornerstone of blind SQLi and it can happen in one of two different ways.</p>
<p>Firstly, it might just throw an exception if no record is returned. Often developers will <em>assume</em> that a record referred to in a query string exists because it’s usually the app itself that has provided the link based on pulling it out of the database on another page. When there’s no record returned, things break. Secondly, the app <em>might not</em> throw an exception but then it also won’t display a record either because the equivalency is false. Either way, the app is implicitly telling us that no records were returned from the database.</p>
<p>Now let’s try this:</p>
<pre>1 and
(
  select top 1 substring(name, 1, 1) from sysobjects where id=(
    select top 1 id from (
      select top 1 id from sysobjects where xtype='u' order by id
    ) sq order by id desc
  )
) = 'a'</pre>
<p>Keeping in mind that this entire block replaces <em>just the query string value</em> so instead of “?id=1” it becomes “?id=1 and…”, it’s actually only a minor variation on the earlier requests intended to retrieve table names. In fact the main different is that rather than attempting to cause an exception by converting a string to an integer, it’s now an equivalency test to see if the first character of the table name is an “a” (we’re assuming a case-insensitive collation here). If this request gives us the same result as “?id=1” then it confirms that the first table in sysobjects does indeed begin with an “a” as the equivalency has held true. If it gives us one of the earlier mentioned two scenarios (an error or shows no record), then we know that the table <em>doesn’t</em> begin with an “a” as no record has been returned.</p>
<p>Now all of that only gives us the first character of the table name from sysobjects, when you want the second character then the substring statement needs to progress to the next position:</p>
<pre>select top 1 substring(name, 2, 1) from sysobjects where id=(</pre>
<p>You can see it now starts at position 2 rather than position 1. Of course this is laborious; as well as enumerating through all the tables in sysobjects you end up enumerating through all the possible letters of the alphabet until you get a hit then you have to repeat the process for each character of the table name. There is, however, a little shortcut that looks like this:</p>
<pre>1 and
(
  select top 1 ascii(lower(substring(name, 1, 1))) from sysobjects where id=(
    select top 1 id from (
      select top 1 id from sysobjects where xtype='u' order by id
    ) sq order by id desc
  )
) &gt; 109</pre>
<p>There’s a subtle but important difference here in that what’s it doing is rather than checking for an individual character match, it’s looking for where that character falls in the ASCII table. Actually, it’s first lowercasing the table name to ensure we’re only dealing with 26 characters (assuming alpha-only naming, of course), then it’s taking the ASCII value of that character. In the example above, it then checks to see if the character is further down the table than the letter “m” (ASCII 109) and then of course the same potential outcomes as described earlier apply (either a record comes back or it doesn’t). The main difference is that rather than potentially making 26 attempts at guessing the character (and consequently making 26 HTTP requests), it’s now going to exhaust all possibilities in only 5 – you just keep halving the possible ASCII character range until there’s only one possibility remaining.</p>
<p>For example, if greater than 109 then it must be between “n” and “z” so you split that (roughly) in half and go greater than 115. If that’s false then it must be between “t” and “z” so you split that bang in half and go greater than 112. That’s true so there’s only three chars left which you can narrow down to one in a max of two guesses. Bottom line is that the max of 26 guesses (call it average of 13) is now done in only 5 as you simply just keep halving the result set.</p>
<p>By constructing the right requests the app will still tell you everything it previously did in that very explicit, rich error message way, it’s just that it’s now being a little coy and you have to coax the answers out of it. This is frequently referred to as “Boolean-based” SQL injection and it works well where the previously demonstrated “Union-based” and “Error-based” approaches won’t fly. But it’s also not fool proof; let’s take a look at one more approach and this time we’re going to need to be a little more patient.</p>
<h4>Disclosure through patience: Time-based blind injection</h4>
<p>Everything to date has worked on the presumption that the app will disclose information via the HTML output. In the earlier examples the union-based and error-based approaches gave us data in the browser that explicitly told us object names and disclosed internal data. In the blind boolean-based examples we were <em>implicitly</em> told the same information by virtue of the HTML response being different based on a true versus a false equivalency test. But what happens when this information can’t be leaked via the HTML either explicitly or implicitly?</p>
<p>Let’s imagine another attack vector using this URL:</p>
<p><span style="text-decoration: underline;"><span style="color: #0000ff;">http://widgetshop.com/Widgets/?OrderBy=Name</span></span></p>
<p>In this case it’s pretty fair to assume that the query will translate through to something like this:</p>
<pre>SELECT * FROM Widget ORDER BY Name</pre>
<p>Clearly we can’t just starting adding conditions directly into the ORDER BY clause (although there are other angles from which you could mount a boolean-based attack), so we need to try another approach. A common technique with SQLi is to terminate a statement and then append a subsequent one, for example like this:</p>
<p><span style="text-decoration: underline;"><span style="color: #0000ff;">http://widgetshop.com/Widgets/?OrderBy=Name;SELECT DB_NAME()</span></span></p>
<p>That’s a pretty innocuous one (although certainly discovering the database name can be useful), a more destructive approach would be to do something like “DROP TABLE Widget”. Of course the account the web app is connecting to the database with needs the rights to be able to do this, the point is that once you can start chaining together queries then the potential really starts to open up.</p>
<p>Getting back to blind SQLi though, what we need to do now is find another way to do the earlier boolean-based tests using a subsequent statement and the way we can do that is to introduce is a delay using the WAITFOR DELAY syntax. Try this on for size:</p>
<pre>Name;
IF(EXISTS(
  select top 1 * from sysobjects where id=(
    select top 1 id from (
      select top 1 id from sysobjects where xtype='u' order by id
    ) sq order by id desc
  ) and ascii(lower(substring(name, 1, 1))) &gt; 109
)) 
WAITFOR DELAY '0:0:5'</pre>
<p>This is only really a slight variation of the earlier examples in that rather than changing the number of records returned by manipulating the WHERE clause, it’s now just a totally new statement that looks for the presence of a table at the end of sysobjects beginning with a letter greater than “m” and if it exists, the query then takes a little nap for 5 seconds. We’d still need to narrow down the ASCII character range and we’d still need to move through each character of the table name <em>and</em> we’d still need to look at other tables in sysobjects (plus of course then look at syscolumns and then actually pull data out), but all of that is entirely possible with a bit of time. 5 seconds may be longer than needed or it may not be long enough, it all comes down to how consistent the response times from the app are because ultimately this is all designed to manipulate the observable behaviour which is how long it takes between making a request and receiving a response.</p>
<p>This attack – as with all the previous ones – could, of course, be entirely automated as it’s nothing more than simple enumerations and conditional logic. Of course it could end up taking a while but that’s a relative term; if a normal request takes 1 second and half of the 5 attempts required to find the right character return true then you’re looking at 17.5 seconds per character, say 10 chars in an average table name is about 3 minutes a table then maybe 20 tables in a DB so call it one hour and you’ve discovered every table name in the system. And that’s if you’re doing all this in a single-threaded fashion.</p>
<h4>It doesn’t end there…</h4>
<p>This is one of those topics with a heap of different angles, not least of which is because there are so many different combinations of database, app framework and web server not to mention a whole gamut of defences such as web application firewalls. An example of where things can get tricky is if you need to resort to a time-based attack yet the database doesn’t support a delay feature, for example an Access database (yes, some people actually do put these behind websites!) One approach here is to use what’s referred to as <a href="http://technet.microsoft.com/en-us/library/cc512676.aspx">heavy queries</a> or in other words, queries which by their very nature will cause a response to be slow.</p>
<p>The other thing worth mentioning about SQLi is that two really significant factors play a role in the success an attacker has exploiting the risk: The first is input sanitisation in terms of what characters the app will actually accept and pass through to the database. Often we’ll see very piecemeal approaches where, for example, angle brackets and quotes are stripped but everything else is allowed. When this starts happening the attacker needs to get creative in terms of how they structure the query so that these roadblocks are avoided. And that’s kind of the second point – the attacker’s SQL prowess is vitally important. This goes well beyond just your average TSQL skills of SELECT FROM, the proficient SQL injector understands numerous tricks to both bypass the input sanitisation and select data from the system in such a way that it can be retrieved via the web UI. For example, little tricks like discovering a column type by using a query such as this:</p>
<p><span style="text-decoration: underline;"><span style="color: #0000ff;">http://widgetshop.com/Widget/?id=1 union select sum(instock) from widget</span></span></p>
<p>In this case, error-based injection will give tell you exactly what type the “InStock” column is when the error bubbles up to the UI (and no error will mean it’s numeric):</p>
<p><img title="" alt="Operand data type bit is invalid for sum operator." src="http://lh6.ggpht.com/-c9drKzJTJyA/Ufd7LQfJsdI/AAAAAAAAFj0/QWXqQVSNtxw/image3.png?imgmax=800" width="500" height="112" border="0" /></p>
<p>Or once you’re totally fed up with the very presence of that damned vulnerable site still being up there on the web, a bit of this:</p>
<p><span style="text-decoration: underline;"><span style="color: #0000ff;">http://widgetshop.com/Widget/?id=1;shutdown</span></span></p>
<p>But injection goes a lot further than just pulling data out via HTTP, for example there are <a href="https://www.pentesterlab.com/from_sqli_to_shell/from_sqli_to_shell.pdf">vectors that will grant the attacker shell on the machine</a>. Or take another tangent – why bother trying to suck stuff out through HTML when you might be able to just create a local SQL user and remotely connect using SQL Server Management Studio over port 1433? But hang on – you’d need the account the web app is connecting under to have the privileges to actually create users in the database, right? Yep, and plenty of them do, in fact you can find some of these <a href="https://www.google.com.au/search?num=100&amp;safe=off&amp;q=filetype%3Aconfig+inurl%3Aweb.config+inurl%3Aftp+%22user+id%3Dsa%22&amp;oq=filetype%3Aconfig+inurl%3Aweb.config+inurl%3Aftp+%22user+id%3Dsa%22">just by searching Google</a> (of course there is no need for SQLi in these cases, assuming the SQL servers are publicly accessible).</p>
<p>Lastly, if there’s any remaining doubt as to both the prevalence and impact of SQLi flaws in today’s software, just last week there was <a href="http://www.computerworld.com/s/article/9241084/SQL_flaws_remain_an_Achilles_heel_for_IT_security_groups">news of what is arguably one of the largest hacking schemes to date</a> which (allegedly) resulted in losses of $300 million:</p>
<blockquote><p>The indictment also suggest that the hackers, in most cases, did not employ particularly sophisticated methods to gain initial entry into the corporate networks. The papers show that in most cases, the breach was made via SQL injection flaws -- a threat that has been thoroughly documented and understood for well over than a decade.</p></blockquote>
<p>Perhaps SQLi is not quite as well understood as some people think.</p>
</div>
<p>文章选自：<a href="http://www.troyhunt.com/2013/07/everything-you-wanted-to-know-about-sql.html#?utm_source=hackernewsletter&amp;utm_medium=email">everything-you-wanted-to-know-about-sql</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e4%bd%a0%e9%9c%80%e8%a6%81%e4%ba%86%e8%a7%a3%e7%9a%84sql%e6%b3%a8%e5%85%a5%e6%8a%80%e6%9c%af/">你需要了解的SQL注入技术</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/%e4%bd%a0%e9%9c%80%e8%a6%81%e4%ba%86%e8%a7%a3%e7%9a%84sql%e6%b3%a8%e5%85%a5%e6%8a%80%e6%9c%af/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>配置wordpress博客阻止部分IP访问</title>
		<link>http://blog.zhourunsheng.com/2012/08/%e9%85%8d%e7%bd%aewordpress%e5%8d%9a%e5%ae%a2%e9%98%bb%e6%ad%a2%e9%83%a8%e5%88%86ip%e8%ae%bf%e9%97%ae/</link>
		<comments>http://blog.zhourunsheng.com/2012/08/%e9%85%8d%e7%bd%aewordpress%e5%8d%9a%e5%ae%a2%e9%98%bb%e6%ad%a2%e9%83%a8%e5%88%86ip%e8%ae%bf%e9%97%ae/#comments</comments>
		<pubDate>Thu, 02 Aug 2012 09:31:59 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[Web设计]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1633</guid>
		<description><![CDATA[<p>上个月，博客安装了没一个星期，就被莫名的IP攻击了，一个月的流量早早用完了，导致博客整整歇业了将近20天，这个 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2012/08/%e9%85%8d%e7%bd%aewordpress%e5%8d%9a%e5%ae%a2%e9%98%bb%e6%ad%a2%e9%83%a8%e5%88%86ip%e8%ae%bf%e9%97%ae/">配置wordpress博客阻止部分IP访问</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>上个月，博客安装了没一个星期，就被莫名的IP攻击了，一个月的流量早早用完了，导致博客整整歇业了将近20天，这个月的第一天，8月1号，估计攻击的ip还在沉睡中，没出现什么异常，到了8月2号，就又开始攻击了。</p>
<p>今天下载分析了一下apache日志文件，找到了这几个攻击的ip，详情如下：</p>
<pre>113.108.76.195 - - [02/Aug/2012:00:10:02 -0400] "GET /favicon.ico HTTP/1.0" 301 584 "-" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1) QQBrowser/6.0"
113.108.76.196 - - [02/Aug/2012:00:10:04 -0400] "GET /favicon.ico HTTP/1.1" 301 584 "-" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1) QQBrowser/6.0"
114.86.181.181 - - [02/Aug/2012:00:10:05 -0400] "GET /favicon.ico HTTP/1.1" 301 583 "-" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1) QQBrowser/6.0"
116.237.76.204 - - [02/Aug/2012:03:04:37 -0400] "GET /favicon.ico HTTP/1.1" 301 583 "-" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1) QQBrowser/6.0"
202.105.139.117 - - [02/Aug/2012:04:02:01 -0400] "GET /favicon.ico HTTP/1.1" 301 584 "-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; CIBA; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.2)"
218.17.162.137 - - [02/Aug/2012:04:12:04 -0400] "GET /favicon.ico HTTP/1.1" 301 584 "-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; CIBA; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.2)"</pre>
<p>主要就是这六个IP，反复请求favicon.ico文件，平均每秒发送几十个数据请求，导致博客一天的最大流量能达到3.5个G，太可怕了。</p>
<p>实在没有找到更好的解决办法，目前只能配置.htaccess文件来阻止这些ip的访问，暂时加入黑名单，配置如下，在文件的最开头加入：</p>
<pre>Order Allow,Deny
Deny from 113.108.76.195
Deny from 113.108.76.196
Deny from 114.86.181.181
Deny from 116.237.76.204
Deny from 202.105.139.117
Deny from 218.17.162.137
Allow from all</pre>
<p>先过几天看看情况，如果还不能解决的话就写一个插件，动态记录ip地址的访问，把那些每秒访问几十次的ip全部加入黑名单，定时清理和更新！</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2012/08/%e9%85%8d%e7%bd%aewordpress%e5%8d%9a%e5%ae%a2%e9%98%bb%e6%ad%a2%e9%83%a8%e5%88%86ip%e8%ae%bf%e9%97%ae/">配置wordpress博客阻止部分IP访问</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2012/08/%e9%85%8d%e7%bd%aewordpress%e5%8d%9a%e5%ae%a2%e9%98%bb%e6%ad%a2%e9%83%a8%e5%88%86ip%e8%ae%bf%e9%97%ae/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>为WordPress建立Html格式的Sitemap</title>
		<link>http://blog.zhourunsheng.com/2012/06/%e4%b8%bawordpress%e5%bb%ba%e7%ab%8bhtml%e6%a0%bc%e5%bc%8f%e7%9a%84sitemap/</link>
		<comments>http://blog.zhourunsheng.com/2012/06/%e4%b8%bawordpress%e5%bb%ba%e7%ab%8bhtml%e6%a0%bc%e5%bc%8f%e7%9a%84sitemap/#comments</comments>
		<pubDate>Sun, 24 Jun 2012 03:56:39 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[Web设计]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[sitemap]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1626</guid>
		<description><![CDATA[<p>熟悉wordpress建站的童鞋都知道sitemap的重要性，尤其是对网站的SEO以及各大重要搜索引擎的索引都 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2012/06/%e4%b8%bawordpress%e5%bb%ba%e7%ab%8bhtml%e6%a0%bc%e5%bc%8f%e7%9a%84sitemap/">为WordPress建立Html格式的Sitemap</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>熟悉wordpress建站的童鞋都知道sitemap的重要性，尤其是对网站的SEO以及各大重要搜索引擎的索引都有重要的作用，wordpress已经有许多优秀的sitemap生成插件可以使用，不过他们大部分生成的都只是xml格式的数据，可以直接提交到搜索引擎，方便搜索引擎的索引，但是不方便访客的阅读。</p>
<p>本文介绍一下怎样客制化和建立自己的Sitemap Page页面，可以包括或者排除特定的页面，特定的分类，加入自己定制的信息等等。<span id="more-1626"></span></p>
<h2>建立步骤</h2>
<p>1. 为了代码的重用，我们需要利用wordpress主题模板的特性（theme partial），在当前的主题文件夹下面新建立一个partials文件夹，然后在里面新建立一个sitemap.php文件，内容如下：</p>
<pre>&lt;h2 id="authors"&gt;Authors&lt;/h2&gt;
&lt;ul&gt;
&lt;?php
wp_list_authors(
array(
'exclude_admin' =&gt; false,
)
);
?&gt;
&lt;/ul&gt;

&lt;h2 id="pages"&gt;Pages&lt;/h2&gt;
&lt;ul&gt;
&lt;?php
// Add pages you'd like to exclude in the exclude here
wp_list_pages(
array(
'exclude' =&gt; '',
'title_li' =&gt; '',
)
);
?&gt;
&lt;/ul&gt;

&lt;h2 id="posts"&gt;Posts&lt;/h2&gt;
&lt;ul&gt;
&lt;?php

// Add categories you'd like to exclude in the exclude here
$cats = get_categories('exclude=');
foreach ($cats as $cat) {
echo "&lt;li&gt;&lt;h3&gt;".$cat-&gt;cat_name."&lt;/h3&gt;";
echo "&lt;ul&gt;";
query_posts('posts_per_page=-1&amp;cat='.$cat-&gt;cat_ID);
while(have_posts()) {
the_post();
$category = get_the_category();
// Only display a post link once, even if it's in multiple categories
if ($category[0]-&gt;cat_ID == $cat-&gt;cat_ID) {
echo '&lt;li&gt;&lt;a href="'.get_permalink().'"&gt;'.get_the_title().'&lt;/a&gt;&lt;/li&gt;';
}
}
echo "&lt;/ul&gt;";
echo "&lt;/li&gt;";
}

//Add Custom Post Types to your HTML Sitemap
foreach( get_post_types( array('public' =&gt; true) ) as $post_type ) {
if ( in_array( $post_type, array('post','page','attachment') ) )
continue;

$pt = get_post_type_object( $post_type );

echo '&lt;h2&gt;'.$pt-&gt;labels-&gt;name.'&lt;/h2&gt;';
echo '&lt;ul&gt;';

query_posts('post_type='.$post_type.'&amp;posts_per_page=-1');
while( have_posts() ) {
the_post();
echo '&lt;li&gt;&lt;a href="'.get_permalink().'"&gt;'.get_the_title().'&lt;/a&gt;&lt;/li&gt;';
}

echo '&lt;/ul&gt;';
}
?&gt;
&lt;/ul&gt;</pre>
<p>2. 拷贝一份当前主题下面的page.php，并重新命名为page-sitemap.php,编辑内容如下：</p>
<pre>&lt;?php
/*
Template Name: Sitemap Page
*/
?&gt;
&lt;?php get_header(); ?&gt;
&lt;div id="content"&gt;
&lt;?php if (have_posts()) : ?&gt;&lt;?php while (have_posts()) : the_post(); ?&gt;
&lt;!-- menu --&gt;
&lt;div id="map"&gt;
&lt;div class="browse"&gt;现在位置 ＞&lt;a title="返回首页" href="&lt;?php echo get_settings('Home'); ?&gt;/"&gt;首页&lt;/a&gt; ＞&lt;?php the_title(); ?&gt;&lt;/div&gt;
&lt;div id="feed"&gt;&lt;a href="&lt;?php echo get_option('swt_rsssub'); ?&gt;" title="RSS"&gt;RSS&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;!-- end: menu --&gt;
&lt;!-- entry --&gt;
&lt;div class="clear"&gt;&lt;/div&gt;
&lt;div class="entry_box_s"&gt;
&lt;div class="entry"&gt;
&lt;div class="page" id="post-&lt;?php the_ID(); ?&gt;"&gt;
&lt;?php the_content('More &amp;raquo;'); ?&gt;
&lt;div class="clear"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;?php get_template_part('/partials/sitemap'); ?&gt;
&lt;/div&gt;
&lt;!-- end: entry --&gt;
&lt;div class="clear"&gt;&lt;/div&gt;
&lt;i class="lt"&gt;&lt;/i&gt;
&lt;i class="rt"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="entry_sb"&gt;
&lt;i class="lb"&gt;&lt;/i&gt;
&lt;i class="rb"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;?php endwhile; ?&gt;
&lt;?php endif; ?&gt;
&lt;/div&gt;
&lt;!-- end: content --&gt;
&lt;?php get_sidebar(); ?&gt;
&lt;?php get_footer(); ?&gt;</pre>
<p>主要修改两个地方，一个是在文件的顶部加入</p>
<pre> &lt;?php
/*
Template Name: Sitemap Page
*/
?&gt;</pre>
<p>另一个是调用sitemap.php,加入</p>
<pre> &lt;?php get_template_part('/partials/sitemap'); ?&gt;</pre>
<p>3. 新建立一个页面，模板选择“Sitemap Page”，示例 <a href="http://blog.zhourunsheng.com/sitemap/">http://blog.zhourunsheng.com/sitemap/</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2012/06/%e4%b8%bawordpress%e5%bb%ba%e7%ab%8bhtml%e6%a0%bc%e5%bc%8f%e7%9a%84sitemap/">为WordPress建立Html格式的Sitemap</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2012/06/%e4%b8%bawordpress%e5%bb%ba%e7%ab%8bhtml%e6%a0%bc%e5%bc%8f%e7%9a%84sitemap/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
