April 2009 - 文章

神威顯赫

話說小小師姐殺手從飛牛牧場回來後,隔天週一中午便接到幼稚園老師電話,說兒子發燒到38.5度,於是只上了半天課就接回家休息。看了醫生,診斷是腸胃型感冒,開了些藥。不過除了晚上吐了一次,間歇性發燒到38度多外,完全沒有喉痛、咳嗽、打噴嚏、流鼻水等傳統感冒症狀,想起醫生推斷的另一種可能,小孩玩太累時也會有這類狀況。

請假在家休息了兩天,定期吃藥,但體溫仍偏高,不時還越過38度警戒線。但奇怪的是,體溫變化速度頗快,常常先量到38多,幾分鐘再量一次又變37多,一分鐘後再量又回到38,我們一度懷疑是耳溫槍有問題,在其他人身上做了對照測試,多次測量結果卻又挺一致的。

週四早上體溫只有37度出頭,心想請假請過頭小孩心就懶了,決定正常送去幼稚園上課,但他一到教室就跟老師說想睡覺,老師見怪不怪安排他到午休區跟幾個小朋友躺著小睡(沒錯,據說飛牛回來後,有好幾個小朋友都有相似的"過勞"症狀),不過到了午餐時間,這小子懶洋洋地拖到全班都開始午睡還在數飯粒,於是中午媽媽就又接回家休息。

週五到週末狀況稍好,體溫慢慢回穩到37上下,雖然也會吃也會玩,但總覺兒子有些精神不濟,一副提不起勁的樣子。到了晚上不用催不用哄,自個兒嚷著想睡,乖乖上床拉了被子就睡,十分異常。媽媽還發現他脕上講了不少夢話,什麼被關在箱子裡、馬桶比人還高的,天曉得在做什麼怪夢。

週日晚上臨睡前從客聽到他在臥室乒乒乓乓,接著就哭了起來。衝進房間一看,原來他不知為什麼在房間裡摔了一大跤,還不小心咬到舌頭流血,幸好破洞不大,沒多久就血就止住。

週一早上再量體溫,不妙,又升到38度了。

碰巧,前幾天媽媽跟朋友聊到兒子的近況,深諳此道的朋友提醒這有點像"小孩子被嚇到",媽媽跟我商量,心想這次感冒不像感冒、發燒狀況時好時壞,加上精神狀況不佳也拖了近一週,是有些異常,不如去收收驚求個心安也好。於是速速連絡了經驗豐富的阿媽領軍,帶隊去行天宮收驚(我在上班沒跟到),回來帶了幾個米糕,阿媽囑咐最少要吃一口。

我自有記憶以來沒收過驚,也沒現場觀摩過,不知收驚的儀式與感覺。收驚當天,兒子回家後還是沒什麼精神,晚上又早早自己上床睡覺。

但說來奇怪,一覺醒來,家裡的小病貓忽然又變回呱噪小老虎。據媽媽說,從早上到晚上我回家這段期間,這小子似乎要把一個星期少說的話補回來,上下學一路上說話唱歌,回到家繼續唱歌說話,又回復成那個神采奕奕剛換完電池的小猴子。

我對宗教神鬼的研究不多(唉! 時間都拿來搞程式了),單依科學邏輯的角度,本案例所蒐集到的事證無從支持我做出任何肯定推論,而真人實事又不像電腦程式可以搞個Lab反覆測試驗證。我只能說,收驚前後的兒子判若兩人,是巧合還是其中有什麼玄妙? 無從得知。但至少這一切應了我們的祈願,讓我不禁要高聲讚嘆: 恩主公神威顯赫,靈驗無比!!

Posted 30 April 2009 03:17 AMJeffrey | 3 comment(s)
Filed under:
XMLDOM Extension Plugin for jQuery

It's surprising to me that we can use jQuery to query and manipulate XMLDOM cross browsers. Like the example below:

var x = $("<xml><rows><row a='ATTR'>FIRST</row></rows></xml>");
x.find("row").attr("a", "Cool!").text("It rocks");
alert(x.find("row").attr("a") + " " + x.find("row").text());
x.find("rows").append("<mark>Awesome</mark>");//Exception in IE
alert(x.find("mark").text());

As you see, I can find the "row" XML node, change its attribute and innerText, then append a childNode from a XML string... Unfortunately, append(xmlString) will cause exception in IE.

Not wanting to go back to use loosy XMLDOM API, I decide to write a simple XMLDOM extension plugin to solve this problem.  The jquery.xmlext.js extend appendXml(), prependXml(), afterXml(), beforeXml() methods which accept string as argument and work like append(), prepend(), after() and before() on XMLDOM.  The xml() is added to provide cross-browser outerXML dumping.

The $("<xml>...</xml>") method uses XHTML to simulate XML, some details will be lost, like case-sensitivity.  jQuery.parseXml() provides cross-browser string to XMLDOM conversion.

/*
* jQuery XML Extension plugin
* Version 1.1 Beta (30-APR-2009)
* by Jeffrey Lee, http://blog.darkthread.net
*
* Examples:
$(function() {
var x = $("<xml><items><center /></items></xml>");
x.find("center").appendXml("<bottom />").prependXml("<top />");
x.find("center").afterXml("<after />").beforeXml("<before />");
alert(x.xml());
});
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*/
; (function($) {
    function mnpXml(opCode, xmlStr) {
        return this.each(function() {
            if (typeof xmlStr != "string") return;
            if (!jQuery.isXMLDoc(this)) return;
            var node = $.parseXml(xmlStr).firstChild.cloneNode(true);
            switch (opCode) {
                case "append":
                    this.appendChild(node);
                    break;
                case "prepend":
                    if (this.childNodes.length > 0)
                        this.insertBefore(node, this.firstChild);
                    else
                        this.appendChild(node);
                    break;
                case "after":
                    if (this.nextSibling)
                        this.parentNode.insertBefore(node, this.nextSibling);
                    else
                        this.parentNode.appendChild(node);
                    break;
                case "before":
                    this.parentNode.insertBefore(node, this);
                    break;
            }
        });
    }
    $.fn.extend({
        appendXml: function(s) {
            return mnpXml.call(this, "append", s);
        },
        prependXml: function(s) {
            return mnpXml.call(this, "prepend", s);
        },
        afterXml: function(s) {
            return mnpXml.call(this, "after", s);
        },
        beforeXml: function(s) {
            return mnpXml.call(this, "before", s);
        },
        xml: function() {
            var elem = this[0];
            return elem.xml || (new XMLSerializer()).serializeToString(elem);
        },
        innerXml: function() {
            var s = this.xml();
            var i = s.indexOf(">"), j = s.lastIndexOf("<");
            if (j > i)
                return s.substring(i + 1, j);
            else
                return "";
        }
    });
    $.extend(jQuery, {
        parseXml: function(xmlStr) {
            if (window.ActiveXObject) {
                var xd = new ActiveXObject("Microsoft.XMLDOM");
                xd.async = false;
                xd.loadXML(xmlStr);
                return xd;
            }
            else if (typeof DOMParser != "undefined") {
                var xd = new DOMParser().parseFromString(xmlStr, "text/xml");
                return xd;
            }
            else return null;
        },
        toXml: function(obj, nodeName, useAttr) {
            var x = $($.parseXml("<" + nodeName + " />"));
            var n = x.find(":first");
            for (var p in obj) {
                if (useAttr)
                    n.attr(p, obj[p]);
                else
                    n.appendXml("<" + p + " />").find(p).text(obj[p]);
            }
            return x[0];
        }
    });
})(jQuery);

You can download jquery.xmlext.js to play or test it directly on Mini jQuery Lab with these code:

$.getScript("jquery.xmlext.js", function() {
            var xd = $.parseXml("<items><center /></items>");
            var x = $(xd);
            x.find("center").appendXml("<bottom />").prependXml("<top />");
            x.find("top,bottom").afterXml("<After />").beforeXml("<Before />");
            alert(x.xml());
            var obj = { Name: "Darkthread", Score: 100 };
            xd = $.toXml(obj, "Person");
            alert($(xd).xml());
            xd = $.toXml(obj, "Person", true);
            alert($(xd).xml());
});

 

 【中文摘要】

jQuery可以用來解析XMLDOM,真是件爽快的事,我一直對XMLDOM的那堆API很感冒,就拿簡單的新增Node來說,一下是createElement,再setAttribute,又要appendChild的,頗為煩人。jQuery除了可以讀XmlNode,還可以用來修改Attribute及InnerText。例如以下的程式:

var x = $("<xml><rows><row a='ATTR'>FIRST</row></rows></xml>");
x.find("row").attr("a", "Cool!").text("It rocks");
alert(x.find("row").attr("a") + " " + x.find("row").text());
x.find("rows").append("<mark>Awesome</mark>");//Exception in IE
alert(x.find("mark").text());

很不幸地,以上的寫法在IE裡會敗在append()上。由於不想為了這點小問題回頭去攪和XMLDOM那一堆囉嗦的API,我決定寫一個小Plugin解決這個問題,增加appendXml(), prependXml(), afterXml(), beforeXml()四個API,除了傳入參數限定是XML字串,其餘用法與append(), prepend(), after(), before()相同,另外我還順手加上xml()一併解決跨瀏覽器轉換成XML字串的需求。另外,我發現原先$("<xml>..</xml>")的玩法是用XHTML去模擬XML,會損失一些XML特性,例如: 節點名跟屬性名的大小寫,故還是應回歸正途,用XMLDOM來做,所以我加了一個jQuery.parseXml()。

大家可以下載 jquery.xmlext.js回去玩,或是直接在 Mini jQuery Lab 用以下程式做測試:

$.getScript("jquery.xmlext.js", function() {
            var xd = $.parseXml("<items><center /></items>");
            var x = $(xd);
            x.find("center").appendXml("<bottom />").prependXml("<top />");
            x.find("top,bottom").afterXml("<After />").beforeXml("<Before />");
            alert(x.xml());
            var obj = { Name: "Darkthread", Score: 100 };
            xd = $.toXml(obj, "Person");
            alert($(xd).xml());
            xd = $.toXml(obj, "Person", true);
            alert($(xd).xml());
});

有遇到問題的再跟我說吧!

TIPS-jQuery.filter的條件化執行效果

jQuery.filter()可以對現有結果集合再進行一次篩選,只保留符合條件者。在某些情境下,我們也可以借用它達到條件化執行的效果。

讓我們看一個實例,有個Table:

<table border="1">
<tr><td></td><td></td></tr>
<tr><td></td><td></td></tr>
<tr><td></td><td></td></tr>
<tr><td></td><td></td></tr>
<tr><td></td><td></td></tr>
</table>

我們希望第一個<td>填入0-4,第二個<td>則第一列寫First,最後一列寫Last,其餘留白。依直覺,程式可以寫成這個樣子:

$("table tr").each(function(i) {
    var tr = $(this);
    tr.find("td:first").text(i);
    var allTr = tr.parent().children();
    if (allTr.index(tr) == 0) 
        tr.find("td:eq(1)").text("First");
    if (allTr.index(tr) == allTr.length - 1) 
        tr.find("td:eq(1)").text("Last");
});

已經夠短了吧?

不,還有簡化空間,祭出.filter()+:first-child/:last-child,程式碼還可以再精簡:

$("table tr").each(function(i) {
    $(this).find("td:first").text(i).end()
    .filter(":last-child").find("td:eq(1)").text("Last*").end().end()
    .filter(":first-child").find("td:eq(1)").text("First*");
});

如上例所示,接在filter()後方的find()/text() Method只有在找到相符結果時會被執行;但即使無符合結果,後面的Method仍會一一被呼叫,因此我們可以用end()回到先前的結果集合再加上第二個filter()做另一項比對,全部的邏輯串成一列就搞定,很酷吧!

TIPS-在Vista上開啟DTC

跑程式時出現以下錯誤:

MSDTC on server 'JEFFREYVISTA\SQLEXPRESS' is unavailable.

很明顯地是MSDTC未開放網路存取所致,對號稱"資深分易式交散茶包射手"的我來說,這問題連茶葉梗都算不上,正想依循古法排除問題,才發現糗大了。

我先從控制台Programs and Features的Turn Windows features on or off想找到熟悉的Enable network DTC access,才發現Vista裡的項目分類方式早已大改,摸了半天亳無所獲。於是我換個方式,想透過元件服務介面設定。沒想到在Vista裡,原本放在Administrative Tools下的元件服務捷徑也不見了...

不得已,只好忘懷自己曾經很會射DTC茶包的事實,乖乖研究在Vista下的處置方法。所幸TechNet上有篇文章詳細地闡述了在Vista/Windows 2008下啟用DTC存取的方法,還很貼心地列出建議的設定,照方煎藥,藥到病除。

加掛某個js導致VS2008 JScript IntelliSense失效

JScript IntelliSense是我鍾愛的VS2008新功能之一,在MS正式採用jQuery後,VS2008進一步還支援jQuery Intellisense,更是讓人雀躍。

無奈,在加掛某些js後(例如jquery-ui.js),你可能會看到如下錯誤:

VS2008抱怨Error updating JScript IntelliSense,而錯誤訊息指出載入的js某一列有問題。但可以確定,該JS程式在瀏覽器上是正常無誤,應屬VS2008在解析Javascript時的問題。如果只是加掛的JS沒有IntelliSense,這問題就不足掛齒;但事實並非如此,只要發生這類解析錯誤,連原有jquery.js的說明提示都會一併停擺,應驗了"一顆老鼠屎,壞了一鍋粥"。

遇到這類問題,最佳解是設法取得*-vsdoc.js檔案,但常常無法如願。所以我們得自力救濟,一開始我想到的做法是將有問題的js改用$.getScript()載入,但與該js相依的程式碼就必須而移在getScript()的complete事件再執行。程式在document.ready()之後一段時間才執行,會讓使用者察覺到延遲,而且想一想,為了設計期間的問題更改架構實在不是明智之舉。

後來我想到個新解法,弄個空白檔案,取名為jquery-ui-vsdoc.js放在jquery-ui.js的同目錄下。這樣子雖然無法提供對jquery-ui.js各函數的提示,但至少不會搞爛原本可用的jQuery IntelliSense,操作步驟也十分簡便。大家在面對類似狀況時,不妨一試。

SlideFieldset Plugin for jQuery

This is my first released jQuery plugin.  The goal is simple -- to add a plus or minus symbol in front of the legend text and make the <fieldset> collapsibile.

To use this plugin, please wrap your whole fieldset content into a <div>, then add references to jquery.slidefieldset.js and jquery.slidefieldset.css.  The style file needs plus.gif and minus.gif, so please keep them under the same folder with jquery.slidefieldset.css.

Use $(...).slideFieldset() to make fieldsets collapsible, here is the sample.

<head>
    <title>Slide Fieldset Sample</title>
    <link href="style/jquery.slidefieldset.css" rel="stylesheet" type="text/css" />
    <script src="js/jquery-1.3.2.js" type="text/javascript"></script>
    <script src="js/jquery.slidefieldset.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function() {
            $("#fsTest,#fsTest2").slideFieldset();
        });
    </script>
</head>
<body>
<fieldset id="fsTest">
<legend>Label</legend>
<div>Content</div>
</fieldset>
<fieldset id="fsTest2">
<legend>Label</legend>
<div>Content</div>
</fieldset>
</body>

【中文摘要】

這是我第一次發表jQuery Plugin,程式很簡單,用途也很單純。就是在<fieldset>的<legend>前方加上一個+/-符號,點選之後可以將<fieldset>的內容收合起來節省空間。

使用時,請先用一個<div>將fieldset的所有內容包起來,然後再參照jquery.slidefieldset.js與jquery.slidefieldset.css,接著$(...).slideFieldset()就可以讓你的fieldset變身成一台"小摺" XD。由於會用到plus.gif與minus.gif兩個圖檔,請將這個圖檔也放在jquery.slidefieldset.css所在目錄下。

如果發現使用上的問題,請在此留言。

飛牛牧場

托兒子的福,幼稚園辦了兩天一夜的飛牛牧場旅遊,長年足不出戶的阿宅老爸才有機會被逼出老巢,體會一下玩樂與消費的奧義。飛牛牧場離二高通霄交流道不遠,位於山谷的牧場有一片又一片令人心曠神怡的翠綠草原,點綴著"味道"十足的小群牛羊...

 

也許是見過世面太少,或許是時代已在阿宅未察之間遽變,園區規劃比我預期的精緻許多。建物很有歐風洋味,乍看之下,有時真有身處國外的錯覺。下方的白色小屋據稱是某偶像劇的拍攝場景,很遺憾,該劇我一秒也沒看過,沒能在心中激起任何漣漪,只知在小屋前可以花20元買牧草餵羊,還開發票。

 

園區植了不少漂亮的花草,可以感受到經營的用心。如果只限出入口刻意經營的迎賓花草便罷,難能可貴的是,隔天清晨我依例展開了全園走透透的"撈本行程",幾乎巡遍園間每個角落,意外發現就連常人罕至(是的,我不太正常)的冷門小徑,也明顯維持定期打掃,少有路面落葉樹枝雜沓的荒涼感,魔鬼在細節裡,能落實到這個程度真的很不簡單。

 

牧場的主角當然是牛羊,園區有很多牛、羊、兔,且都可以餵食,但簡單的進食行為演化成有趣的商業運作模式。本來由場方購買或收割飼料、再由飼養人員將食物交給動物的流程做了大幅改良;先將食材進行包裝,以特定價格販售給不特定第三者,再以不專業的方式與頻率將食物交付給動物。場方節省下的餵養人力需求,由自願付費的廣大不特定第三者們取代之,同時還可提高第三者的滿足感及滿意度,真是無比划算了。不對,這個設計太完美了,其中一定有被忽略的成本...  我猜是可憐的牛羊們,吃頓飯也被這麼折騰,心中應該挺悶的。想像一下,公司將原本免費供應給專案團隊的午餐便當以一個一百元的價格賣給客戶,客戶要求與Project Team進行供應便當的午餐會報,但限定講完一張投影片才可以吃一口,這樣夠機車吧? 我猜這是牛羊的感受。不過,我想牛羊朋友大人有大量,就小小犠牲自己讓苦悶的城市人們尋求一些心靈的慰藉吧!

  

  

園區裡有三隻迷你馬可以讓小朋友在半個籃球場大小的圍欄中騎著走三圈,於是家中兩個小子比老子更早有了騎馬的經驗。

早上趁著小朋友們DIY做冰淇淋、手工餅乾的上課時間,又去園區小晃了一下,聽到有餵羊奶比賽,推老婆大人在最後一刻參賽,結果才發現報名參賽是要付費的,而且一小瓶奶快的話不用10秒就可以嗑光,算來頗貴。所幸娘娘不負重望拿下第一,光光榮榮地領了第一名的獎品--竟是一小包乾牧草外加可以溜羊(好寒酸)。與其說這隻可憐的小羊是我們的"獎勵",不如說我們是牠的"刑具",就見牠死命地想要上演"小羊逃",我們則像牽了頭拉不住的大狗,那包乾草顯然也沒發揮任何吸引力,都快塞到鼻孔了牠連正眼也不瞧一下。歷經了一分鐘"手上牽小羊,心中趕羚羊"的掙扎與拉扯,我們決定不要做自私自利沒心沒肺的可惡人類,乖乖地領著這頭一點都不"敬業"的小羊還給工作人員。(工作人員特別提醒一口沒吃的乾草料可以餵其他的羊,看來小羊不敬業也不是一天兩天,這一切都是場方的陰謀啦~~~)

   

值得一提的是,牧場有不少乳製品,上圖的冰淇淋十分香濃,雖然我沒吃過傳說中口碑極佳的北海道冰淇淋,但我想美味程度應該相去不遠吧?! (看有沒有兩種都吃過的人要發表評鑑心得) 早餐時的冰鮮奶及自製優格也很不錯,大飽口福的結果,兩天下來一身奶味,哈!!

忘了說,此行最大的額外收獲是親眼看到傳說中與兒子"青梅竹馬"的"學姐",有個已是大班的可愛小女生,成天老愛找我兒子玩,三不五時跑來牽他的手,然後兒子也就乖乖跟著走。她還很愛從正面一把抱起兒子,看得我這個做爹的都心生幾分羨慕兼嫉妒... 最有趣的是昨夜營火晚會結束,要走過一段黑黑的林道回住宿區,就見她一路用手電筒照路,不時叮嚀兒子要小心腳步,最後要各自回房時還不忘在兒子臉上來一記Kiss Bye,搞得我不禁嫉火中燒: 為什麼,這傻不楞登的小子竟有此等魔力引來小女生垂青? 為什麼我沒有? ...  不過,幾過兩天觀察,我發現好幾次小女生一路牽著手走完路程後,會將兒子帶到我跟前,一副"現在交給你們"的世故。這兩小無猜的可愛互動中,應有不少是姐姐疼愛弟弟的成份,當是傻呼呼的兒子特別能激起比較年長女生的母性吧! (謎之聲: 為什麼,這結論聽起來有點酸酸的?) 但小孩子的天真,除了讓已經習慣於私欲算計的成人狂呼卡哇伊之外,似乎也找不到其他更貼切的形容詞了。

Posted 20 April 2009 12:56 AMJeffrey | 4 comment(s)
Filed under:
jQuery教學9-15上線

經過長達數月的夙夜匪懈、焚膏繼晷,15集的jQuery教學終於在爆肝前一刻驚險完成...

連結如下,有興趣學習jQuery的朋友可以參考看看。各位大德有任何批評指教,也歡迎在此留言,老衲必當虛心檢討改善。

後面的7集教學中,我用jQuery做了兩個有趣的實作練習,一個是用拖拉"刁十三支"的操作介面(告訴大家一個祕密,我這輩子還沒真的刁過 XD),另一個則是純HTML式的新增修改刪除介面,只配合一隻ASPX作為與資料來源溝通的管道,ASPX可以換成任何Web平台的語言(Java Servlet、PHP,甚至是Perl...)。

這些看似複雜的介面用jQuery做來都是幾十行程式就搞定,簡潔程度令人髮指(如果你有用過JS徒手打造過動態網頁就會舉雙手贊同),我覺得對所有必需染指網頁開發需求的黑手來說,jQuery是絕不可錯過的神兵利器,不要再拿棍子石塊打打殺殺了,快試試這把烏茲衝鋒槍吧!

[2009-06-04更新] 發現邊做邊學 jQuery 系列 15- AJAX式內容管理介面範例程式使用Firefox執行時會發生HTTP 411錯誤,解決方法為修改Default.htm第30列,由
$.post("WebApi.aspx?mode=list", null, bindList, "json");改為$.post("WebApi.aspx?mode=list", "", bindList, "json");即可,並在此致歉。

隱含殺機的GET式AJAX資料更新

jQuery的出現讓AJAX網頁的開發瞬間變簡單了。只要寫支簡單的ASPX,用Request["..."]接入前端用jQuery.ajax()傳來的參數,馬上就實現了AJAX式的資料查詢、新增、修改、刪除功能。但是,小心不要寫出如下的程式碼:

protected void Page_Load(object sender, EventArgs e)
{
    if (Request["mode"] == "del")
    {
        try
        {
            CheckCookieForAuthentication();
            CheckPermission();
            Guid id = new Guid(Request["id"]);
            DataProcessor.DeleteData(id);
            Response.Write("OK");
        }
        catch (Exception ex)
        {
            //...Blah...
        }
        Response.End();
    }

看出問題在哪裡嗎? SQL Injection? new Guid(Request["id"])確保參數id必須是GUID,不致被塞入惡意程式碼。權限管控? CheckCookieForAuthentication()會檢查登入成功才有的Cookie作為身分識別、接著檢查使用者是否有權執行,看來夠嚴格。XSS? 除了id外沒有接受其他的輸入參數,因此沒有資料內容驗證的問題。那麼,梗呢? 梗到底在哪裡?

由這段程式的寫法來看,我們可以用webform.aspx?mode=del&id=[guid]的方式執行。換句話說,這支程式同時支援GET Request或POST Request呼叫,而這犯了兵家大忌!!!

永遠不要使用GET方式接收指令進行資料更新!

為什麼GET式的更新很危險? Browser不是都有XmlHttpRequest(XHR)跨網域限制嗎? User必須登入該網站,才能使用XHR對該ASPX進行存取,如果想要在第三方網頁加掛程式碼以XHR進行XSS攻擊,應該會因跨網域被擋下來,不是嗎?

講到重點了,XHR是有跨網域限制,但<script src="...">沒有!

想像一個情境,假設有個芭樂小站部落格平台,用以上的GET寫法執行部落格文章的刪除。小白是芭樂小站站長,登入網站介面執行一些操作後,就跑去網路上打混閒晃。小黑是個駭客,想要刪除某篇看不爽的PO文,先查出該文的Blog GUID,然後寄封信給小白,在信件先嵌入幾張連到網路相簿的照片,例如: 殺很大那種,<img src="httq://albumweb/someimage.jpg" />,最後多加個<img src="httq://blah.com.tw/admin/delPost.aspx?mode=del&id=[guid]" style="display:none;" />。小白的瀏覽器剛做完部落格管理(或者在本機上保存了登入成功的Cookie)再收取WebMail,雖然WebMail多會警告該信內嵌外部資源,預設不下載及Show圖,但當然是敵不過想看圖的衝動滴。按下同意顯示圖片,由albumweb下載照片的同時,也會觸發delBlog.aspx?mode=del&id=[guid],此時瀏覽器會沿用先前登入成功的Cookie,以站長權限執行刪除作業。就這樣,圖也看了、文也刪了,還真的"殺很大"。

(是不是有些人現在才發現WebMail或Outlook外連圖片資安警告的用意? 它還被用來防止另一種更常見的機車伎倆,例如: 廣告信在圖片src加上你的email當成附加參數,開信看圖就等同宣告你的email是有效的,而且心腸很軟,有信都要稍微看一下以示禮貌,以後就會有收不完的廣告信了。XD )

GET式AJAX資料更新的最大問題在於可以用<img src="...">之類的方法閃避XHR的跨網域限制。造成了可以從第三方的網頁盜用使用者Cookie、Session對原站發動攻擊的漏洞,因此在嚴謹的設計中,許多Web Service是完全禁止GET式存取的。例如: 在ASP.NET AJAX裡,所有WebMethod只接受HTTP POST verb,更進一步還要求檢核Content-Type: application/json標頭,做到雙重保險。

在以上的例子裡,我們可以加上(Request.HttpMethod == "POST")的檢查限阻止GET請求,若是用WCF等機制預設都有相關保護,不要隨意亂改設定即可。但這些都只算基本防護,如果要談更進一步的管控,可以在登入完成後,在網頁上產生一個Token(不要放入Cookie,降低被盜風險),在執行與這個網頁相關的資料查詢、更新動作時,一併傳回Token驗明正身,這樣便可以將更嚴格限定只能由該網頁發出相關需求。

在網路世界裡,處處埋藏殺機,開發者不可不慎。

70萬人次紀念

感謝大家熱烈捧場,本站累計參觀人次突破70萬囉!!

今天稍早我隨手在噗浪上發表了倒數消息,所以破天荒第一次,有正式踩到70萬人次的得主誕生--艾小克先生... 不過沒有獎品啦! 等100萬人次時再來辦個小活動回饋一下鄉親~~~

【成長歷程】

Posted 14 April 2009 08:41 PMJeffrey | no comments
Filed under:
ReportViewer的跨瀏覽器問題

測試了一下,ReportViewer 2008在跨瀏覽器方面問題挺多。

例如報表工具列,只有在IE下可以完整顯示,線上列印功能要動用ActiveX Control,所以IE以外的瀏覽器無法使用天經地義。但測試下來,在IE以外的瀏覽器上仍然有些其他問題要克服...

在非IE瀏覽器裡,即使ReportViewer Width、Height指定100%,也只能顯示一小塊。

我發現是<table id="ctl00_ContentPlaceHolder1_ReportViewer1" cellspacing="0" cellpadding="0" style="display: inline-block; height: 100%; width: 100%;">裡inline-block搞的鬼,由於它不是標準CSS,在非IE瀏覽器上就爆炸了。我想到一個解決方法是在ReportViewer外包一個<div id="dvWrapper">,再用jQuery("#dvWrapper table:first").css("display", "")可以解決這個問題。但好戲在後頭...

在Firefox裡還OK的報表工具列,到了Chrome、Safari裡就爛掉了,在Opera裡工具列是好的,但報表的標題列是壞的。

過關的有: IE, Firefox, Opera

壞掉的有Chrome, Safari,原本應該一列的工具列硬生生被拆成三列...

追了一下,問題出在這段CSS的詮釋:

<div style="display:inline;">DIV1</div> <table style="display:inline;"> <tr><td>Table1</td></tr> </table> <div style="display:inline;">DIV2</div>

關鍵在於,當<div>、<table>宣告display:inline時,是否還該維持強制換行? 我不是CSS專家,不知誰對誰錯,但至少在這個案例中,不再是IE一國,其他瀏覽器一國,IE所在的一方,票數還略勝一籌哩!!

【結論】採用ReportViewer時,還是乖乖用IE就好,不要想太多~~

Javascript .apply()應用實例

之前談過this與Closure,當時JS前輩Ammon提到了apply與call。今天在寫程式時剛好有機會用到,便順手整理了一下。

一般我們呼叫Function時,都會寫成funcName(arg1, arg2, ...)的形式。但像jQuery裡的事件函數,都用this來存取觸發事件的元素,若想由程式自行呼叫這些事件函數,就只能由元素去trigger()事件。傳統funcName()的呼叫方式,因無法指定this,顯然就無法滿足這種情境下的需求。

apply()call()是Function型別的兩個Method,apply()的語法是funcName.apply(thisArg, argsArray),第一個傳入的參數在就是函數中的this,而第二個參數則可傳一個參數陣列給該函數。call()與apply()用途相同,唯一的差別在於,call()必須一一明列出參數,例如funcName.call(thisArg, arg1, arg2, ...)。

底下是個例子,我寫了一個Button Click事件,其中用了this.value去取Button的顯示文字,並做一些後續的處理。範例裡只是個alert,但如果這些處理邏輯很複雜,我們想應用在Button Click以外的其他用途,依正統的程式架構設計,應將這段邏輯拆出來變成獨立函數,這樣子就可以被Button Click事件以及其他程式所共用。

但如果函數是執行期間動態指定的,或者你只是臨危受命加兩行程式救火(火燒屁股時還堅持做Refactoring應該會被蓋布袋),此時apply()就可以幫上大忙。在以下的例子裡,我假造了一顆"偽"按鈕,由於btnClick中只會用到this.value,我就宣告一個value屬性充數。就這樣,沒有Button也能享用Button Click事件囉!

<script type='text/javascript'>
$(function() {
function btnClick() {
    alert("Button[" + this.value + "] clicked!");
}
$("#a").click(btnClick);
var virtualButton = { value: "V" };
btnClick.apply(virtualButton);
});
</script>
</head><body>
<input type="button" value="A" id="a" />
</body>
ReportViewer on IIS7

在IIS7上執行ReportViewer+rdlc的報表,IE8不斷跳出'RSClientController' is undefined等一連串Javascript Error。忽略Error後出現的操作畫面,呈現圖檔找不到的紅X。

換成FF,看得更明白了,HTTP 404找不到檔案。

Google到這篇文章,看來是Reserved.ReportViewerWebControl.axd的問題,我這才想起自己處理過這問題,這次再遇上竟沒了印象。網路上的文章是透過修改IIS解決,但實際上在IIS7上會出錯是因為VS2008在修改web.config時,事情只做了一半。

看來,寫Blog除了分享心得給眾人做功德,也多少彌補了中年人江河日下的記憶力,真是一舉數得。(為什麼?? "一舉數得"這四字寫來竟有一股淡淡的哀傷? XD)

IE8: 用不同身份連上同一台Web

改用IE8後,發現有個地方很不方便。

在我們的測試環境中,常常同時要用不同身份連上同一台Web Server。之前使用IE7/IE6時,只要另外開一個IE連上同一網站,IE每次都會跳出來問帳號密碼,因此可做到每個IE各用不同身份登入的效果。

IE8做了一些改變,讓多個Tab或多個IE視窗共享一個Process,以節省資源,改善效能。但副作用是,當你在一台Web完成登入後,之後再開Tab、甚至另開IE連上同一網站,都會共享相同的Session。這意味著另開Tab或另開的IE都會承襲先前登入的身份、接續剛才的Session,如此下來,等同被剶奪一人分飾多角的能力。

IE8提供了一個-nomerge參數可解決這個問題,為了方便使用,我在桌面建了一個新的捷徑: IE8-New

當要用新的Session連上網站時,改點這個連結就OK了。

Posted 08 April 2009 05:02 PMJeffrey | 9 comment(s)
Filed under: ,
Live Messenger強迫換ID?

今早收到一個MSN訊息,寄送者署名Windows Live Messenger Service工作小組:

重要服務宣告: 因應近期系統增強的需要,您必須變更您的電子郵件地址以登入 Windows Live(TM) Messenger Service。
若要確保您的存取權限不被封鎖或了解更多相關資訊,請移至 http://support.microsoft.com/gp/Messenger/zh-tw

連到http://support.microsoft.com/gp/Messenger/zh-tw,看沒有很懂,所以我切成英文http://support.microsoft.com/gp/Messenger/en-us,但覺得該文較強調改變ID後一切如舊,對於為何要換ID的解釋還是讓我一頭霧水。

您可能會收到來自 Windows Live Messenger Service 工作人員的立即訊息。

擁有您正在使用網域 (電子郵件地址 @ 符號後面的部份。) 的公司,已為其公司的即時通訊系統保留該網域。如果您收到來自 Windows Live Messenger Service 工作人員的重大服務通知,要求您必須變更您的登入 ID 才能夠繼續登入 Messenger,即表示此項變更對您有影響。我們建議您在收到通知後立即進行變更。變更登入 ID 的程序非常簡單。如果您沒有進行變更,不久後您將在登入時收到一則錯誤訊息。您必須在變更您的登入 ID 之後,才可使用 Windows Live Messenger Service。

我用的是Hotmail信箱登入MSN Messenger,照此說來,是Hotmail公司要把@hotmail.com的網域拿去自己用(猜不透為啥要這麼幹),所以我們必須讓出@hotmail.com,意思是指Hotmail信箱不能用在MSN上了? Google到另一肩網誌,算是有較合理的解釋,@hotmail.com可以直接改用@live.com繼續接關再戰。 但MS實在沒有理由做這樣動搖國本的決定? 也完全沒看到相關的宣導與說明,我有點懷疑。

重點來了,要怎麼變更? 透過前文裡的連結進入Live ID的管理,根本沒地方可以設定,再深入研究了一下,發現Hotmail的Live ID是不能改的(佐證),另外我還找到一些類似的抱怨

到目前為止,我無法判定這是有人假造Windows Live Messenger Service Staff送出的惡作劇(4/1早過了好唄! 專案Delay?)、系統失誤誤發[更新: 已接獲通知證實為系統誤發]、還是釣魚網坫失敗的攻擊行動。整個導引流程看來很怪,但連上的網站卻又是真的,莫非是有人想要透過DNS或攔截盜轉網址進行攻擊但失敗了。

目前能蒐集到關於此一更換ID程序的消息還不太多,如果有進一步的資訊我再補充上來。

【2009-04-08 11:45更新】
微軟的Moli幫我向Microsoft PR求證過,證實這個訊息是"系統誤發"的,完全誤會一場,請大家直接忽略即可!
Statement attributable to a Microsoft spokesperson
We are aware that some Windows Live Messenger users received an instant message from the “Windows Live Messenger Service Staff” account today. The notice asked customers to change their email address in order to continue using Windows Live Messenger. The notice was made in error. Consumers should ignore the message and not change their Windows Live email address. Microsoft apologizes for any inconvenience this may have caused.

 

更多文章 下一頁 »

搜尋

Go

<April 2009>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789
 
RSS
【工商服務】
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication