Quantcast
Channel: 黑暗執行緒
Viewing all 2311 articles
Browse latest View live

HTML5 筆記 - 使用 SVG 取代 PNG/GIF 圖示

$
0
0

隨著主流瀏覽器支援度愈來愈好,在網頁裡用 SVG 取代 PNG、GIF 呈現圖示(Icon)及簡單圖案漸成趨勢,沒吃過豬肉也要看看豬走路,趕緊做點功課並整理筆記備忘。

網頁引用 SVG 有三種做法:

  • Object: <embed src="…"> <object data="…"> <iframe src="…">
  • Inline: <svg>…</svg>
  • Image: <img src="…"> background: url('…')

SVG 於 2001 年問市,標榜向量圖型縮放無鋸齒、靠宣告就能做動畫,還可與 JavaScript 互動。早期瀏覽器(IE6 開始)支援 SVG 需依賴外掛套件,必須以 Object 方式(<embed> <object> <iframe>)嵌入 SVG 檔,由外掛套件負責呈現及互動操作,JavaScript 能做的互動很有限。

現代瀏覽器均已內建 SVG 支援,在 HTML 可直接加入 <svg> 成為網頁一部分,使用上與一般網頁元素沒什麼兩樣,可經由 JavaScript 存取修改繪製內容,可透過 CSS 控制顏色、線條粗細(例如: svg path { stroke: black; stroke-width: 1px; fill: none; }),變化無窮。(註: 如要使用較新的 CSS 技巧,需留意 IE 及 Edge 是否支援)

來段簡單示範。我在以下網頁我用 JavaScript / jQuery 動態產生 SVG 畫出三角形,並在按鈕事件修改 <svg width> 放大到 100px 寬,另外用 CSS :hover 樣式製造出線條加粗並灰階化的滑鼠滑過特效。Live Demo

<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>SVG Demo 1</title> </head><body><style>
    svg:hover path {
      stroke: #444;
      stroke-width: 2px;
      fill: #ccc;
    }</style><script src="https://code.jquery.com/jquery-3.1.0.js"></script><button id="sc">Scale Up</button><div style="padding: 3px;"><svg id="s"></svg></div><script>
    var s = $("#s");
    //使用JavaScript產生SVG
    s.attr("viewBox", "0 0 20 20");
    s.attr("width", 20);
    s.html('<path stroke="red" fill="yellow"></path>');
    s.find("path").attr("d", "M2 2 L18 10 L2 18 Z");
    $("#sc").click(function() { s.attr("width", 100); });</script></body></html>


效果展示:

 

除了以 <svg> 形式嵌入 HTML,另一種流行用法是用 SVG 取代 PNG/GIF 做為 <img> 或 CSS background/content 的圖案來源,適用所有原本引用圖檔的場合(例如元素的背景圖),卻能保有放大縮小不失真的向量特性。其用法跟 .png/.gif 相同,只是將 <img src=""> 或 CSS: background: url(…) 連結換成 .svg 即可。還是一種做法是使用 Data URI,將 SVG 內容直接嵌入 HTML 或 CSS,如此可省去瀏覽器另發 Request 下載 svg 的效能損耗,加快網頁顯示速度。示範如下:

SVG Data URI 除了以 data:image/svg+xml;utf8, <svg>…. 直接串接 XML 內容,亦可將 <svg>…</svg> 內容轉為 Base64 編碼,但如此反而會增加資料量(3 Bytes 變成 4 Bytes,外加 0-3 個填充字元)並失去可讀性及可編輯性。除非要避免 HTML 或 CSS 摻雜 <svg> 干擾閱讀,不然 SVG Data URI 直接放入 XML 內容較有利。

但要注意,IE 不支援以 Data URI 內嵌 SVG,只能連結外部 .svg。如以下測試,兩個 SVG 圖檔分別使用 Data URI 及獨立 .svg 檔方式載入,Chrome / Edge 兩種做法都行(測了 Firefox/Safari 也都 OK),IE11 只支援外部 .svg 作為 <img> 來源,唉...

簡單幾何圖案算算座標或敲指令就可產生,若要求精緻美觀,則是視覺設計人員及專業設計軟體的領域,程序猿們最好閃開讓專業的來。開發時如需要現成圖示圖案,網路可找到不少免費向量圖示,多半有提供 SVG 格式下載,前端攻城獅們可多加利用。

參考資料:


【茶包射手日記】用 TransactionScope 包 LINQ 迴圈查詢出錯

$
0
0

使用者報案某網頁功能故障,經抽絲剝繭鎖定爆炸點在一段「TransactionScope 中以 LINQ to SQL 取回物件集合,跑 foreach 開連線查資料庫」邏輯(警語:跑迴圈執行資料庫查詢,迴圈次數如過高將成效能殺手,宜避免),簡化為以下程式片段可重現問題:(註: 程式碼經過簡化,在 foreach 迴圈裡不斷開啟資料庫連線看起來很突兀,但實際案例中 foreach 迴圈是呼叫外部程式庫的共用函式,要追進去才會知道其中開了連線查詢資料庫)

using (var tx = new TransactionScope())
{
	using (var db = GetLinqToSqlDataContext())
	{
		//using (var tx2 = new TransactionScope(TransactionScopeOption.Suppress))
		{
			var datas = from o in db.SomeTable
						where o.Col1 == "A" || o.Col1 == "B"
						select o;
			foreach (var q in datas)
			{
				using (var cn = new SqlConnection(cs))
				{
					cn.Open();
					var cmd = cn.CreateCommand();
					cmd.CommandText = "select getdate() as d";
					var dr = cmd.ExecuteReader();
					dr.Read();
					Response.Write(dr[0]);
				}
			}
		}
	}
}

錯誤訊息為:

[SqlException (0x80131904): There is already an open DataReader associated with this Command which must be closed first.]
   System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon(TransactionRequest transactionRequest, String transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest) +396
   System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransaction(TransactionRequest transactionRequest, String name, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest) +146
   System.Data.SqlClient.SqlDelegatedTransaction.Promote() +134

[TransactionPromotionException: Failure while attempting to promote transaction.]
   System.Data.SqlClient.SqlDelegatedTransaction.Promote() +756746
   System.Transactions.TransactionStatePSPEOperation.PSPEPromote(InternalTransaction tx) +63
   System.Transactions.TransactionStateDelegatedBase.EnterState(InternalTransaction tx) +177

[TransactionAbortedException: The transaction has aborted.]
   System.Transactions.TransactionStateAborted.CheckForFinishedTransaction(InternalTransaction tx) +11
   System.Transactions.EnlistableStates.Promote(InternalTransaction tx) +25
   System.Transactions.Transaction.Promote() +61
   System.Transactions.TransactionInterop.ConvertToOletxTransaction(Transaction transaction) +46
   System.Transactions.TransactionInterop.GetExportCookie(Transaction transaction, Byte[] whereabouts) +193
   System.Data.SqlClient.SqlInternalConnection.GetTransactionCookie(Transaction transaction, Byte[] whereAbouts) +35
   System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx) +450
   System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx) +4889946
   System.Data.SqlClient.SqlInternalConnectionTds.Activate(Transaction transaction) +4890005
   System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction) +33
   System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject) +1286
   System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection) +65
   System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory) +117
   System.Data.SqlClient.SqlConnection.Open() +122
   ASP.testdtc_aspx.Page_Load(Object sender, EventArgs e) in X:\WWW\TestDTC.aspx:28

測試發現如將最外層的 TransactionScope 移掉,或將 foreach 用 new TransactionScope(TransactionScopeOption.Suppress) 包起來不參與交易就不會出錯。

初步推測錯誤原因:LINQ to SQL 查詢傳回的 System.Data.Linq.DataQuery<T> 跑 foreach 並不是一次讀完所有資料再跑迴圈,而是開著 DataReader 每跑一圈讀一筆,當處於 TransactionScope 交易範圍內,此時如試圖另外建立資料庫連線,連線將會升級參與分散式交易,過程中將觸發 There is already an open DataReader associated with this Command which must be closed first. 錯誤。依此原理,先 .ToList() 或 .ToArray() 再 foreach 也可解決問題,經實測也獲得驗證。

進一步推論,問題關鍵在 TransactionScope 中 DataReader 開啟狀態不能再開連線,故即使沒用 LINQ to SQL 也可能踩雷,例如以下程式範例,在 TransactionScope 內 while (dr.Read()) { 開DB連線; } 一樣能引爆,故在 Entity Framework 情境下也可能遇到。

using (var tx = new TransactionScope())
{
	using (var cn1 = new SqlConnection(cs))
	{
			cn1.Open();
		var cmd1 = cn1.CreateCommand();
		cmd1.CommandText = "select 1 union select 2";
		var dr1 = cmd1.ExecuteReader();

		while (dr1.Read())
		{
			using (var cn2 = new SqlConnection(cs))
			{
				cn2.Open();
				var cmd = cn2.CreateCommand();
				cmd.CommandText = "select getdate() as d";
				var dr2 = cmd.ExecuteReader();
				dr2.Read();
				Response.Write(dr2[0]);
			}
		}
	}
}

爬文在 Stackoverflow 找到另一種解法,在連線字串加上 MultipleActiveResultSets=true 開啟 MARS 功能,可解除同時間單一 DataReader 限制避開問題,但有些副作用

  • 針對非 MARS 模式最佳化的老程式搭配 MARS 使用時效能稍差
  • 同時執行多個批次作業時,你跟 SQL Server 會難以斷定 USE、SET、BEGIN TRAN、COMMIT、ROLLBACK 等指令的影響範圍

評估後,將 LINQ to SQL 查詢結果 .ToArray() 再 foreach 是影響最小的做法。

心跳帶心率爆衝問題排除心得

$
0
0

入手第一支 GPS 錶起,多年來已很習慣戴著心跳帶跑步,一方面作為「效能調校」的依據(相同配速,心率愈低代表愈有效率體能愈好),更重要的是提醒自己量力而為不要嗨過頭,一路跑去蘇州賣鴨蛋。多年使用下來,偶爾會遇到心率數字失準狀況,最近又累積一則問題處理經驗,順手整理心得分享兼備忘。

心率帶原理與醫院的心電圖相似,藉由心跳帶的導電橡膠片測量心跳所產生的微小電壓變化,訊號放大後即可由波形推算心跳頻率。因此,心跳帶鬆緊帶內側兩片(我的 Garmin HRM-RUN 舊款心跳帶左右各二共有四片)緊貼皮膚的膠片是導電的,以三用電錶實測中央膠片部分電阻只有 100 歐姆(膠片外框部分不導電)。

因此可知導電性會影響測量結果,於是有個常見現象:戴上心跳帶跑步才在暖身階段,心率便飆上即使全力衝刺都很少達到的破表水準,但隔一陣子再降回正常範圍。長期跑下來,跑多快心跳差不多會有多高都心裡有數,足以斷定這種心跳趨勢明顯不合理,究其原因多半是皮膚太乾燥導電不佳造成,而運動一段時間開始流汗後問題會自然消失,數字便會恢復正常。「不夠潮」所造成的誤差線型類似下圖,典型特徵是開始沒多久心率爆衝,大約 3-5 分鐘後降回預期範圍直到結束。

解決不夠潮問題很簡單,運動前在鬆緊帶的導電片塗點水就成了(心跳帶使用手冊也會提醒,但遇上冬天...)。知名的鐵人部落客 DC Rainmaker 還傳授了幾招私房密技:佩戴前先舔一下(這....)或塗導電凝膠。國外有賣心跳帶專用的導電凝膠,台灣較濕熱,較少乾冷到運動不出汗,沒看過有人在賣,可能要找醫療用的,另外也可以 DIY。依我自己經驗,一般沾點自來水就夠用,但有時要稍等一分鐘確保生效,沾點肥皂效果又更好。

正常情況會像下圖,當保持相同配速,心率會在起跑後快速上升到該配速對應心率區間,之後隨時間緩緩上升。

最近遇到一個比較詭異的案例,因原廠心跳帶脫膠膠片斷裂,我買了便宜的白牌台製心跳帶替換,試跑過一兩回沒什麼問題。但之後陸續出現離譜誤差,最扯的一次是,同樣配速有一段心率低到 100 以下,有一陣子衝上 225,跑出這種心率,拎杯早就歸西了吧?

由於會發生在中後期可排除「不夠潮」,而剛換心跳帶就出狀況,優先懷疑跟新買心跳帶的品質或相容性有關,但也不排除電池沒電或心率監測器故障。印象中電池換不到一年,應該沒那麼快耗盡,而取出電池量電壓仍有 3.05V 左右(新品約 3.2V),加上心率亂跳期間觸地時間跟左右平衡數據無異樣,故先排除電力不足因素。為對照起見,特地找來 Bryton GPS 錶的心跳帶,一次綁兩條,遇到數字有異,立刻將心率監測器換到另一條,實測換心跳帶數字照樣偏高,新買心跳帶洗刷冤屈無保請回。問題回到心率監測器上,推論可能心率偵測耗電較多(或搭配新買心跳帶時需要較多電力),現有電池電壓雖還有 3V 但已在不夠力邊緣,但支持其餘功能(觸地時間、左右平衡、ANT+傳輸)則綽綽有餘,如此可解釋其他數據正常唯有心率亂跑。換上全新電池,心率數字就正常了。心跳帶疑難雜症經驗再添一筆~

最後補充兩篇心跳帶專業文(作者是當年推薦我跑櫻花初馬的馬場前輩 Alex):

ASP.NET 網站部署後發生 ryslon 編譯錯誤-2146232576

$
0
0

同事遇到的案例。

VS2017 新增 ASP.NET 網站專案,專案範本預設參照的 Microsoft.Net.Compilers 版本是 1.3.2。(延伸閱讀:神祕的ASP.NET bin-roslyn目錄) 同事為了使用 C# 7.0 功能,升級 Microsoft.Net.Compilers NuGet 套件到 2.7.0 版,該專案先前已部署測試過,心想 ASP.NET Runtime 版本維持 4.5.2 沒變,應該不會有問題。

殊不知,在開發機的 IIS Express 及 IIS 測試 OK,部署到測試台 IIS,冒出以下錯誤:

在 Stackoverflow 查到一則討論解答了困惑:

The culprit is the Microsoft.Net.Compilers package, used to support modern C# syntax/features (version 6.0, 7.0) in your project and in Razor views in particular. Depending on its version, the package requires a particular minimum version of the full .NET framework to be installed on a machine in question.

For instance, the 2.2.0 package requires .NET 4.6+. Even though your project is targeting say .NET 4.5.2, you probably have the latest .NET installed on your development machine, and everything goes just fine. The remote deployment machine only has .NET 4.5.2 installed, and when your ASP.NET application tries to compile resource (e.g. views) at run time, you get error -2146232576.

意思是新版 Microsoft.Net.Compilers Package 支援在 .cshtml Razor 語法及專案引用 C# 6/7 新語法,但 Package 本身依賴特定的 .NET Framework 版本,即使 ASP.NET 的目標平台是 4.5.2,要使用 Microsoft.Net.Compiler 2.2.0,部署端機器必須安裝 .NET 4.6+,否則在編譯 View 將發生 -2146232576 編譯錯誤。

回頭再看 Microsoft.Net.Compilers Package 說明,原來本草綱目早有記載...

最後,為了簡化日後部署程序,同事選擇降版回 1.3.2 解決問題。而先前我常困惑為什麼在 .cshtml 不能用字串插值 $"…{varName}…",這下也有了解答。

2018 石碇馬

$
0
0

總爬升高度超過一千公尺,在初夏舉辦,又硬又陡的石碇馬,要失心瘋到什麼地步才會年年報到? 201415 1617

天曉得,反正今年我又參加了。

去年全馬人數銳減剩五百人,有點擔心大會會經營困難,今年倒是人氣回升,有好幾組慶祝百馬、兩百馬、三百馬的跑團,熱鬧非凡。

有一輛大會接駁車開錯路,故全馬延遲了幾分鐘出發。

五公里左右彭山公廁旁的小廟 2016 拆除重建,迄今尚未完工。

小廟旁的龍柱靜靜等待著,明年此時不知是否已復行視事。(所以不知不覺間已決定明年還要報名?)

適逢桐花季節,一路上有不少桐花可賞。

花季前期落花不多,加上要趕路,就不排成愛心拍照了,隨便堆個四朵意思到就好。

照片中的鞋印背後有故事。一般來說,剛跑過積水留下鞋印無足為奇,但如果濕鞋印綿延數公里不絕就有點意思了吧?苦追數公里,我終於找到鞋印的主人,一位黑色無袖排汗衫黑短褲穿 New Balance 黃底黑跑鞋的網友(鞋底的黃色類似陸王蠶絲土的顏色),我在心中默默推測鞋內早已汗流成河,辛苦了~

這位攝影大哥很故意,選在一段大陡坡的最高點掌鏡,吆喝著要爬坡爬到牙歪的跑友們快點跑起來,哈!

花了兩小時 44 分完成半馬,來到二格山腳綠豆湯鞍部,眼見天上薄雲快擋不住蠢蠢欲動的太陽,今天就不加碼攻上二格山頂,速速折返。

空氣不太好,沿筆架山望去白茫茫一片。

第一次穿 Y 拖跑石碇馬,深深感覺自己功力不足,下坡腳掌要斜著落地,時間一久腳背連腳踝處的肌肉又酸又僵,連著小腿也緊了來,只能步步為營,無法像跑平地一樣輕鬆。見其他穿 Y 拖的前輩下坡也一臉輕鬆健步如飛,我還有很大的進步空間。

以下為回程順手亂拍,路旁農家的雞。(九點半之後雲 Hold 不住了,太陽露臉)

石碇特產苦茶油麵線店家。(在補給站有吃到,好吃)

八卦茶園。

看到這堆模具就知道終點快到了。

最後,5:38:57 完賽,打破了場地 PB。但我的 Fenix 3 又在 30 公里後 GPS 大錯亂,最後甚至收不到 GPS 訊息,導致最後里程大亂,總距離被灌水到 46K,兩週前三重馬在最後十公里也是 GPS 大暴走,而兩次賽事間跑過數回 10-20K 倒都無異,莫非是手錶硬體出了問題,連續記錄過久元件會過熱? 不過這得連跑 30K+ 實測才能射茶包,太折騰了,再觀察吧。

本屆獎牌挺可愛。

 

最後,推一下大會的用心之處:

全程使用環保杯,補給有苦茶油麵線、翠玉蛋等土產,跟往年一樣多采多姿。

吃到手油油嘴油油有面紙可擦,最難能可貴的,鄉親吶,是凡士林是凡士林是凡士林! 我想起多年前跑萬金石跑到該邊股溝快摩擦生火卻一路找不到凡士林的沈痛回憶,還直接擱在椅子上自取,不怕羞於啟齒,真是太懂得跑者的需要了~

第一名前導車過去很久了,忽然又聽到前導車接近,原來是在引導產地直送的冰塊讓跑友消署。之後一路上有冰汽水、冰啤酒可喝,讚!

小型賽事最怕跑到前無古人後無來者又遇上叉路,大會在所有叉路口都標了黃底箭頭路牌,讓人安心不少。

更衣盥洗室有四支超強力蓮蓬頭,配上冷水,沖起來好痛快~

全馬賽後餐點一樣是在學生餐廳吃冷氣吃飯,今年吃學生自助餐,但我比較懷念去年的排骨飯

CSS 左右貼齊樣式在 IE 產生大段空白

$
0
0

講到 CSS 我充其量只算是票友,但專案遇到問題還是得面對... Orz 本次的案例如下:

使用者回報某網頁用 Chrome 看正常,改在 IE 卻有部分段落的句子前方出現一大段空白,檢視 HTML 原始碼,發現編寫者輸入文字內容時,將段落裡的不同句子拆成多行,前方還加上 Tab 與上方對齊以求美觀。一般來說,HTML 文字出現的空白會被壓縮,接連的換行與空白符號只會被當成一個空白處理(要留白需改用 &nbsp; &ensp; &emsp;),但由於套用 text-align: justify 左右貼齊,這些換行與 Tab 便在 IE 上產生非預期的結果。例如以下範例:

<html><head><meta http-equiv="Content-Type" content="text/html; encoding=UTF-8" /><style>
	article { text-align: justify; }</style></head><body><article><section>故事是這樣的,有個內容網頁,其中一個段落由好幾句話組成。
			製作者輸入內容時,在 HTML 原始碼換行輸入第二句話,前方加了 Tab 好與上一行對齊,各家瀏覽器看來都算正常,除了 IE11...
		</section><article></body></html>

經實測,這段 HTML 在 Chrome、Firefox、Edge 看起來都正常,除了 IE11,

由於 IE 出現的多餘空白會隨瀏覽器視窗寬度變長變短,推測這是 IE 在實作左右貼齊時將換行與 Tab 間隔開的文字視為可以拉長間隔的區塊,靠它調節整行寬度,試著移去換行與 Tab 讓前後文字相連問題即消失,可證明此一假設。掃瞄及修正網頁文字工程浩大,於是我嘗試透過樣式設定補救的做法,找到一解 – text-justify。

text-justify 尚未標準化,各家瀏覽器支援與實作狀況不一。例如,依據 MDN text-justify 有 auto、none、inter-word、inter-character、distribute(已廢除不建議使用) 幾種設定值,而 IE 則有以下選項

  • auto
    預設值,瀏覽器自行決定演算法 (結果搞出一大片留白)
  • distribute
    接近 newspaper 設定,但針對東方語系最佳化(例如: 泰文)
  • distribute-all-lines
    與 distribute 相同,但最後一行也會左右對齊,適用表意文字(Ideographic Text)
  • distribute-center-last
    未支援
  • inter-cluster
    將字與字間無空白文字左右貼齊,適用亞洲文字
  • inter-ideograph
    將表意文字左右貼齊,並同時加大或縮小表意文字及字間空白
  • inter-word
    增加字與字之間的空白,是最快讓所有行寬一致的做法,但不包含段落的最後一行
  • kashida
    阿拉伯文專用的貼齊方式
  • newspaper
    同時增加字母與字的間隙,演算法最複雜,適用於拉丁語系字母

文件涉及不少語言學名詞,我有看沒有懂,實測最準:

如以上展示,auto、inter-cluster、inter-word 會產生多餘空白,distribute、inter-ideograph、newspaper 較正常。最後我選擇用 distribute,用其他瀏覽器觀察也沒什麼問題,以此 Workaround 結案。

【茶包射手日記】IIS Log 檔換日問題

$
0
0

線上主機每天產生數百 MB 的 IIS Log,為避免 Log 檔吃光磁碟空間,我們多會安排排程執行壓縮及清理作業,每天將前一天的 Log 壓成 ZIP 檔,再依「原始 Log 檔保留 N 天,壓縮 Log 保存 M 天,超過 M 天移至後線儲存空間」的原則刪除或搬移檔案。

今天發現某台機器的 Log 清理排程每天準時兩點執行,但 IIS Log 壓縮檔全是空包彈,大小只有 22 Bytes,裡面空空如也。

起初以為是 Log 檔過大壓縮失敗,但經驗中用 7Zip 壓過數 GB 的大檔,加上現場實測用相同的 Batch Script 壓縮前一日的 Log 檔並沒有問題。DIR 檢查 IIS Log 所在目錄,看到所有檔案的更新時間是早上八點,頓時開悟。

改測試壓縮今天的 Log (原本的設計是 5/4 02:00 壓縮 5/3 的 Log),成功重現凌晨排程遇到的狀況,今天的 Log 檔 IIS 仍在使用中,會傳回 The process cannot access the file because it is being used by another process 存取錯誤。而遇此錯誤 7Zip 仍會產生不包含任何內容的空白 ZIP 檔,這就是一堆 22 Bytes ZIP 檔的由來。

原因是 IIS Log 的日期算法預設以 UTC 時間為準,對映到台北時區 u_ex180503.log 的記錄範圍是台北時間 5/3 08:00 - 5/4 08:00,故 5/4 02:00 u_ex180503.log 仍在使用中,要等到 5/4 08:00 IIS 改寫入 u_ex180504.log 後才能拿來壓縮。

要解決此一問題,做法有二:

  • 將排程延到 08:00 後執行,但這違背「維護作業應儘可能排在離峰」原則
  • IIS 有個選項「使用本地時間為檔案命名 / Use local time for file naming and rollover」

    勾選後 IIS 會改在台北時間午夜 12 點切換檔案,例如: 台北時間 5/4 00:00:00 起改寫入 u_ex180504.log。但須注意 Log 裡記錄的時間仍會採 UTC 時間,故 5/4 00:00:00 寫入記錄的時間會記為 5/3 16:00:00。

評估後,決定改用本地時間切檔解決,收工。

英文單字朗讀 MP3 DIY

$
0
0

這是「搶救英文大作戰」的副本任務,小閃光的英文字彙慘不忍睹,我想到把單字表轉成朗讀 MP3 的方法,想讓她照三餐服用看看能否起死回生。

查了一下,文字轉 MP3 大家幾乎都是用 Balabolka,它可以朗讀輸入的文字,有眾多調整選項,還能轉成 MP3,十分方便。

簡單試用,得到幾點心得:

  1. 文字轉語音功能事實上來自 Windows 內建的 SAPI (Microsoft Speech API),意思是我也可以自己寫程式做到類似功能。
  2. 我的 Windows 10 繁體中文版內建四種語音(如上圖所示),其中 David 跟 Zira 只看得懂英文,但聲音比較道地自然;Hanhan 小姐有中英雙聲帶,但英語部分機械感偏重,比不上 David 跟 Zira,Tracy 則只會看中文講廣東話。
    【補充】
    關於語音安裝可參考:下載適用於逼真的閱讀程式]、 [閱讀模式中,及 [大聲唸出的聲音 - Office 支援
    Windows 10 新版似乎又增加了更多選項:附錄 C:TTS 語音 - Windows Help
  3. SAPI 也支援第三方語音來源,例如開源的語音合成專案 – Ekho 餘音,會說廣東話、國語、廣東台山話、韶安客家話、藏語、雅言(中國古代通用语)和韓語等語言。
  4. Balabolka 提供命令列工具 balcon,搭配 LAME (MP3編輯工具) 可用批次指令大量產生 MP3
    balcon.exe -n David -s -2 -t "Hi, I am darkthrad" -o --raw | lame -r -s 16 -m m -h – d:\output\darkthread.mp3

經實測,我偏好英文交給 David 唸,國語由 Hanhan 上場的男女混雙組合,Balabolka 有個「外語詞匯」功能,背後是使用 SAPI5 <voice>標籤標註某段文字使用不同語音朗讀,就能實現「英語給 David 唸,國語讓 Hanhan 說」的效果。例如:

<voice required="Name=Microsoft David Desktop">This is a book.</voice> 這是一本書

另外,要在朗讀過程加入較長停頓,我試出來的方法是在英文中插入"...",在中文插入",",用程式讀取字彙表產生加入<voice>標籤的文字檔再交給 Balabolka 輸出,自製的單字朗讀 MP3 就完成了,收工。

展示影片


Coding4Fun–Microsoft Speach API 筆記

$
0
0

前篇文章用 Balabolka 搞定自製英文單字朗讀 MP3,但老讀者們都猜到接下來會發生什麼事... 是的,C# 整合 SAPI 讓電腦講話的練習來了!

原本以為要裝什麼 SDK 或套件,沒想到 .NET 已內建,專案只需參照 System.Speech 就好。

開始前先看一下你的 Windows 裝了哪些語音以及其支援語系:

static void ListInstalledVoices()
{
    var voice = new System.Speech.Synthesis.SpeechSynthesizer();
    voice.GetInstalledVoices()
        .ToList().ForEach((v) =>
        {
            Console.WriteLine(
                v.VoiceInfo.Name + " " +
                v.VoiceInfo.Culture.DisplayName);
        });
    Console.Read();
}

在我的 Windows 10 繁體中文專業版執行結果如下:

Microsoft Hanhan Desktop 中文 (繁體,台灣)
Microsoft Zira Desktop 英文 (美國)
Microsoft Tracy Desktop 中文 (繁體,香港特別行政區)
Microsoft David Desktop 英文 (美國)

依據文件,Windows 10 2018 4 月更新有增加其他語音選項,國語部分加入了 Zhiwei(志偉?) 跟 Yating (雅婷?)。

來個最基本的應用示範,其中包含前篇文章提到的某段文字用不同語音朗讀。要加入 <voice> 標籤有兩種做法,第一種是自己組 SSML再呼叫 SpeakSsml(),但得自己處理 XML namespace 比較繁瑣,另一種做法是使用 PromptBuilder,透過 AppendText() 加入純文字,用 AppendSsmlMarkup() 加入包含 <voice> 等標籤的 SSML 片段,最後將 PromptBuilder 當成參數交給 Speak() 執行,比拼湊 XML 省事也易讀一些。

static void SayHi()
{
    var voice = new System.Speech.Synthesis.SpeechSynthesizer();
    //美語 男聲
    voice.SelectVoice("Microsoft David Desktop");
    voice.Speak("Hi there, I am darkthread.");
    //美語 女聲
    voice.SelectVoice("Microsoft Zira Desktop");
    voice.Speak("Hi there, I am darkthread.");
    //國語
    var pb = new PromptBuilder();
    pb.StartVoice("Microsoft Hanhan Desktop");
    pb.AppendText("大家好,我是黑暗執行緒");
    //https://msdn.microsoft.com/zh-tw/library/hh378418(v=office.14).aspx
    pb.AppendSsmlMarkup("<voice name=\"Microsoft David Desktop\">darkthread</voice>");
    pb.EndVoice();
    voice.Speak(pb);
    //廣東話
    voice.SelectVoice("Microsoft Tracy Desktop");
    voice.Speak("大家好,我是黑暗執行緒");
}

PromptBuilder除了加入 SSML 標籤,還有其他好用的控制選項,例如:

  • AppendAudio()
    插入外部聲音檔(WMA)
  • AppendBreak()
    插入停頓
  • StartParagraph()/EndParagraph()/StartSentence()/EndSentence()
    形成段落跟句子,模擬自然說話的停頓效果,使語音更逼真
  • StartVoice()/EndVoice()
    指定語音名稱,或指定語系、性別、年齡,由 SAPI 挑選適用的語音

除了前面介紹過的 <voice>,SSML 還有一些有用標籤,可做到精細調控:
(參考:Speech Synthesis Markup Language Reference)

  • emphasis
    強調,加重語氣
  • p、s
    標註段落跟句子,讓說話效果更自然逼真
  • phoneme
    可使用特殊音標指定特定字語的發音,例如指定 Zhou 要唸成「趙」
    His name is Mike <phoneme alphabet=""x-microsoft-ups"" ph=""JH AU"">Zhou</phoneme>
    參考:發音標示符號表 Phonetic Alphabet Reference
  • prosody
    指定範圍內文字的音調(pitch)、速度(rate)、音量(volume)
    Your order for <prosody pitch=""+1st"" rate=""-10%"" volume=""50""> eight books </prosody>
  • voice
    指定範圍內文字使用不同語音

想將語音輸出轉成 WAV 檔也很簡單,在 voice.Speak() 之前先加上 voice.SetOutputToWaveFile("x:\\filename.wav"),一行搞定! 如果要轉成 MP3,則需要整合第三方程式庫或直接用 lame.exe 將 .wav 檔轉 .mp3,lame.exe x:\filename.mav x:\filename.mp3,一樣一行搞定最省事。

又到了久違的呼口號時間:

SAPI 真酷! .NET 好威呀!

2018 海山馬

$
0
0

跑馬至今,海山馬是我唯一年年參加不缺席(20134567)的賽事,並留下目前唯一的落馬經驗。為保持全勤記錄,儘管天氣炎熱賽道單調補給平常獎牌無奇,還是報了名。

六點開跑,全馬賽道跟去年一樣,出發向左往城林橋停車場折返回起點 10K,接著繼續往大漢橋折返回起點再 10K,然後依相同路線跑第二趟湊足 42K。

氣象預報晴天少雲,最高溫 31 度,已有心理準備,但七點不到已陽光普照又沒還是讓人挺崩潰。出太陽又無風的天氣把普通全馬推向極限運動,跑來有滿滿的厭世感。沒什麼心情拍照,決定採「不要逗留,能跑就跑,早結束早解脫」戰略。

天空蔚藍,我心賭爛。

此刻唯一的撫慰,僅存賽道上飛揚的馬尾。

全馬人數很少,只有三百多人,稀釋到總長 10 公里的賽道上,前後幾百公尺都沒人的場面不時上演。

懶得拍照,但這幾棵漂亮大樹錯過可惜。籃球場空無一人,這種天氣瘋子才會在外面跑來跑去曬太陽。(在說誰?)

荷花荷花幾月開?五月就開了。

天氣炎熱,每個水站的大水桶成了救命仙丹。後半馬進水站的 SOP 是喝一杯運動飲料、吃一截香蕉或一片西瓜、再舀三杓水把身體淋濕,渾身涼爽打起精神趕往下一個水站淋水。

這次總算振作一點,走路走得不多,守住 SUB5,總排名擠進前 25%。

賽後照例去看了山羊才回家,整理照片時卻發現羊在青我...

公版獎牌是海山馬的傳統。

     

繼前兩場三重馬石碇馬接連發生 30K 之後 GPS 失準,fenix3 這回又在三小時 40 分左右失去 GPS 訊號,之後時好時壞,里程配速大亂,總距離也飄成 47K。

連續三次全馬都出問題,清一色只發生在連續使用 3 個半小時之後,前面的 GPS 則十分精準,平日練跑到 10-20K 也一切正常,這樣很難再用一時秀逗或巧合解釋。(意思是只要跑進 330 就不會有這困擾了,fenix3 對我的期望好高呀 XD) 不像是軟體問題,只能外行地推測,莫非某硬體元件因連續運作過久過熱導致異常?

二話不說,回程就繞去 Garmin 光華服務中心送修。服務中心的先生小姐聽完病情描述也稱奇,看來是罕見案例(那可以用我的名字命名嗎? 例如:達克舒瑞後天耐力缺乏症候群之類的),手錶得住院檢查,這一兩週就順勢休兵保養足底筋膜。(住院前特拍照留念)

IIS HTTP 重新導向功能筆記

$
0
0

將 IIS 網站的特定網址導向其他網址,有幾種做法:

  1. UrlRewrite 模組
    彈性高,支援複雜的轉換規則(可使用 Regular Expression 定義規則),能在使用者未察覺的情況下完成轉換,例如將 /product/book/computer 轉為 /product.aspx?zone=book&catg=computer,提供較友善的網址並增進 SEO。
    但若轉址是因為網站或網頁搬家,需明白告知並建議使用者改用新網址,UrlRewrite 較不適用。
  2. JavaScript 或 HTML 標籤轉址
    在舊網頁加上 location.href = "新網址" 或 <meta http-equiv="refresh" content="0;url=新網址" /> 將使用者導向新網址。缺點是每個舊址要留一個 HTML,瀏覽器需耗消資源載入 DOM 才執行轉址,也未明確告知客戶端應改用新址。
  3. 伺服器端轉址
    即在 ASP.NET 呼叫 Response.Redirect() 或在 ASP.NET MVC return Redirect() / RedirectPermanet()。
    原理是回傳 HTTP 301/302 告知瀏覽器永久或暫時改用新址。301 是宣告舊址作廢以後請改用新址,搜索引擎下回建立索引及計算排名時會以新址取代舊址;302 則是暫時改用新址,未來每次會先回舊址再轉向新址。HTTP 1.1 增加的 307/308 類似 301/302,差別在於只對 GET 導向,POST 時仍回舊址避免回傳資料遺失。
    參考:HTTP Redirect 301, 302 區別及對SEO的影響 @ 符碼記憶 
  4. IIS HTTP 重新導向模組
    效果同伺服器端轉址,好處是只需設定 web.config 不用寫成網頁,且一條設定即可適用整個子目錄。

來看看 HTTP 重新導向怎麼設定。使用前需先確定 IIS 有安裝 HTTP 重新導向(HTTP Redirection):

接著在 IIS 管理員中找到要導向的資料夾進行設定:

套用設定後,IIS 會在該資料夾建立如下的 web.config:

<?xml version="1.0" encoding="UTF-8"?><configuration><system.webServer><httpRedirect enabled="true" destination="/NewPlace" 
          exactDestination="false" childOnly="true" httpResponseStatus="Found" /></system.webServer></configuration>

關於設定介面中的選項,我用以下的資料夾結構示範,假設我們在 Moved 指定重新導向 /NewPlace。


  • 將所有要求重新導向至確切的目的地(而非目的地相對位置)
    exactDestination="true/false"
    勾選時,/Moved/test.html、/Move/SubFolder/test.html 都會被導向 /NewPlace
    如未勾選,/Moved/test.html 導向 /NewPlace/test.html、/Move/SubFolder/test.html 導向 /NewPlace/SubFolder/test.html
  • 只將要求重新導向至此目錄(不是子目錄)中的內容
    childOnly="true/false"
    勾選時,只有 /Moved/test.html 會被導向新址,更下一層子目錄的 /Move/SubFolder/test.html 不會被導向仍可瀏覽

除了透過 IIS 管理介面,我們也可直接修改 web.config 設定重新導向規則,<httpRecdirect> 參數選項可參考官方文件:HTTP Redirects -httpRedirect- - Microsoft Docs另外,<httpRedirect> 還有一些進階應用,例如:ASP 升級 ASPX 時,將所有 *.asp 導向首頁。參考

以上的做法有個小缺點 - 必須為舊網址建立資料夾放置 web.config,雖然視覺化顯示哪些地方存在舊網址避免誤用是好事,但如果你不喜歡新專案冒出一堆無用資料夾,可以試試這招 - 在根目錄的 web.config 中用 <location path="舊址"> 包覆 <httpRedirect> 宣告,就不必實際建立資料夾或檔案也能針對特定路徑進行轉址。

<location path="Moved"><system.webServer><httpRedirect enabled="true" destination="/NewPlace" /></system.webServer></location>

【茶包射手日記】Visual Studio 專案項目 Icon 突變

$
0
0

同事報案,專案有個類別 .cs 的圖示怪怪的,一般 .cs 的圖示應是綠色的 C#,但問題類別卻是個沒見過的文件圖示(下圖黃框處),比對發現是該類別繼承 System.Net.WebClient 造成(註:這麼做是為了修改 WebClient 的 Timeout),隨便新增一個 ClassN.cs,只要繼承 WebClient 圖示馬上變掉,取消繼承就會恢復。

為了搞懂奇怪圖示的意義,我開始搜尋有沒有 Visual Studio Solution Explorer 圖示大全這種東西,爬文很久發現官方文件沒提供這種東西,最後是在 VS Image Library 找到線索,順便整理過程蒐集到的參考資料:

在 Visaul Studio 2015 圖示清單文件(PDF)中比對到這個奇怪圖示,它的名字叫 Component File:

由圖示知道跟 Component 有關,回頭查 Visual Studio 發現我繞了遠路,其實 .csproj 裡就有線索,當 .cs 繼承 WebClient ,該類別 .cs 會多出 <SubType>Component</SubType>:

<ItemGroup><Compile Include="Class1.cs" /><Compile Include="Class2.cs"><SubType>Component</SubType></Compile><Compile Include="Class3.cs" />

即便手動刪掉,開啟或修改該類別後會再自己長出來。更進一步,Visual Studio 還會為它加上特殊設計檢視:

由蒐集到的關鍵字爬文,在 Stackoverflow 找到完整解釋

會加上 <SubType>Component</SubType> 是因為 System.Net.WebClient 繼承了 System.ComponentModel.Component,修改圖示及套用特殊設計 UI 是 Visaul Studio 針對 System.ComponentModel.Component 所加入的邏輯。要避免可在類別加註 [System.ComponentModel.DesignerCategory("Code")]。

經實測,指定 DesignCategoryAttribute 即可恢復正常。

又學到冷知識。

瀏覽器 HTTP 301 導向記錄清除

$
0
0

前篇文章提到 HTTP 重新導向有 301, 302, 307, 308 幾種形式,其中 301/307 為「永久重新導向」,意思是原網址宣告作廢,請客戶端未來一律改連新址。而各家瀏覽器都忠實貫徹這個精神,一旦接獲伺服器回傳 301 將銘記在心,下回使用者再連到該網址,瀏覽器將省略「連接舊址 -> 接收 301/302 轉址 -> 轉往新址」的過程,自行接轉連新網址。

用以下實驗證明。web.config 加入以下設定,指定將 httq://myserver/darkthread 轉址到 http://blog.darkthread.net

<location path="darkthread"><system.webServer><httpRedirect enabled="true" destination="http://blog.darkthread.net"
        exactDestination="true" httpResponseStatus="Permanent" /></system.webServer></location>

啟用設定後,以 Chrome、IE、Edge、Firefox 連上 httq://myserver/darkthread,將會被轉連到本部落格。

接著我們再修改 web.config,將上述設定的 enable="true" 改成 enable="false",或是乾脆將整個 <httpRedirect>設定刪除。這時再用瀏覽器連 httq://myserver/darkthread,將會發現即使設定已停用或刪除,瀏覽器仍記得先前的永久轉址要求。

之前測試專案不小心敲錯新網址,糗了,被瀏覽器記下來,想改都沒得改,於是我被迫研究清除瀏覽器轉址記憶的方法。

改用無痕視窗可避開記憶的轉址設定,一般瀏覽模式如果要抺去記憶,需清理快取或瀏覽歷程,各家做法不一,實測如下:

IE 要清歷程記錄:

Edge 要清快取資料與檔案:

Chrome 記錄在快取圖片和檔案:

Firefox 放在已快取的網頁內容:

自己的瀏覽器還能洗白,但如果給錯新網址的 HTTP 301 是被訪客瀏覽器或搜尋引擎記住,客人都會像變了心的女朋友,再也回不來惹,使用 HTTP 301 務必要謹慎。(有個保險做法是先設 302 運行一陣子,確認沒問題再改成 301。)

SQL Server 使用者定義型別

$
0
0

在同事報案筆錄看到新鮮玩意兒,資料表的某個欄位型別不是 VARCHAR 不是 DATETIME 不是 INT,而是某個沒看過的名稱(如下圖示意),研究了一下,是所謂的使用者定義型別(User Defined Type, UDT),過去只在教材看過,首次觀察到活體。

使用 UDT 有什麼好處?

UDT 可指定型別、資料長度及精確度、是否允許 NULL,還能定義預設值跟規則,定義一次可重複用於多個資料表、Stored Procedure,實現重用性並維持一致性,良好的 UDT 命名能望文生義,具自我說明(Self-Documentation)效果。另外,UDT 整合 SQLCLR 還能用 C# 實現複雜的邏輯,威力強大。(註:整合 SQLCLR 需留意非原生邏輯執行效能問題[案例])

延伸閱讀:

原本以為 UDT 最大的優點應是 -- 當欄位需要放寬,只需修改 UDT,所有使用它的資料表可一次放寬,雖然直覺技術難度甚高,如能做到價值連城。爬文之後發現是我想多了 - UDT 只能 CREATE 跟 DROP,不能 ALTER,如要更改,必須先另建一個 UDT,將逐一修資料表改用型別。參考 12

雖然不如想像中強大,我還是想試試 UDT 的威力,打算建個 UDT 玩玩:

很快地,我又被潑了一盆冷水 - 無法使用 SSMS 管理介面新增 Defaults 跟 Rules(下圖黃色標示處),只能透過 T-SQL 指令建立。
查了文件,SQL Server 未來將不再支援 CREATE DEFAULTCREATE RULE,已不建議使用,應該是 SSMS 不提供操作介面的原因。


由此看來,UDT 僅存型別與長度統一及自我說明的優勢,好處有限,個人對它沒有愛,不推。

Windows 10 連線 USB 數據機發傳真

$
0
0

很久沒有搞電腦搞到一肚子火了,記錄射茶包經過。

時至今日,生活大小事幾乎都能靠 Email、LINE、網站、APP 搞定,但偶爾仍會遇到只收傳真的店家或公司,例如:訂奶茶、傳信用卡授權書... 等。家裡採購雷射印表機時基於體積及成本效益考量,沒選擇有傳真功能的事務機,代價是久久遇到要傳真的場合,就得傷一下腦筋。

小七傳真一張 A4 土匪價 15 元,線上傳真服務便宜又方便,但我的原則是「個資上網能免則免」。評估之後決定花幾百塊買個 USB 數據機,配合 Windows 內建傳真軟體,在家裡插上電話線自己就能發傳真最安全方便。

USB Modem 不貴,白牌產品三百有找,最後我選了一支 Lenovo 的(感覺有廠牌好點,而且如果接 ThinkPad 有問題,客服得負責到底,哈),含運費 360 大洋入手。

接上 USB Modem,Windows 成功自動識別及安裝驅動程式,但顯示裝置為序列埠 COM10,從 Lenovo 官網下載驅動程式更新後變成 USB Modem。

開啟 Windows 傳真和掃描(Windows Fax and Scan),設定好本機傳真(設定說明可參考這篇:HP 和 Compaq 桌上型電腦 - 在 Windows 7 和 Windows Vista 環境下使用電腦傳送傳真 - HP®顧客支持

實測發現問題,發送時文件會被送到預設印表機,操作視窗則卡住數分鐘後彈出操作逾時錯誤,傳真文件也沒出現在送件匣。

還有另一個問題,我注意到多了一台名為 Fax 的印表機,查看印表機內容時卻彈出「這部電腦沒有安裝'Microsoft Shared Fax Driver'印表機驅動程式。除非您安裝了驅動程式,否則無法存取印表機內容,您現在要安裝驅動程式」。同一時間測試接收傳真,USB Modem 發出令人懷念的數據機吟唱,證明驅動程式跟硬體都正常,所以問題只卡在傳送。

 

猜想 Windows 10 是以印表機概念模擬傳真發送行為,Fax 印表機缺少驅動程式應是無法發送傳真的原因。我在網路上有找到第三方提供的 Microsoft Shared Fax Driver 檔案下載,但既然是微軟自家驅動程式,應該 Windows 內建或由官方下載,沒理由安裝來路不明的版本。爬文抱怨找不到 Microsoft Shared Fax Driver 的討論不少,但大多發生在 XP 等舊版 Windows,
畢竟事務機普及又有網路傳真,Windows 10 時代還用 USB Modem 傳真的人已如鳳毛麟角。找到的討論沒什麼標準答案,
不外乎檢查硬體是否故障,確定數據機驅動程式有裝好之類的罐頭建議。還不時看到「你重灌 Windows 試試」之類的回文,
看得我一肚子火,誰都知道重裝治百病,你要來幫我重灌?(搞不定時火氣特大,王藍田上身)

沒什麼頭緒,另外了桌機 Windows 10 Enterprise 試手氣,沒想到順利無比一次 OK,發送傳真會觸發撥號,讓我士氣大振,意味著硬體跟驅動都是沒問題的,純粹是環境因素。而我還發現一個事實 - 在桌機 Windows 並未出現 Fax 印表機。看來 Fax 印表機並非必要,而問題機的 Fax 印表機缺乏驅動程式,則可能是關鍵。手動將 Fax 印表機刪除,問題仍未解決,試著移除 Windows 傳真和掃描軟體再重裝,發現傳送傳真時會跳出以下對話框,回答是會自動新增 Fax 印表機:

新增的 Fax 印表機這回有了驅動程式,可檢視印表機內容:

重新再試一次,問題排除,傳真發送成功。

留下疑點,FAX 印表機是否是必要的?

查到這篇 MS Blog 文章:Installing fax and not seeing the fax printer created (and not getting an error either)。裡面提到若遠端桌面連線殘留 Fax 印表機,則在安裝傳真元件時將不會新增正確的 Fax 印表機也不會有錯,但其狀況跟我不同。文章還提到若不慎將 Fax 印表機刪除,可使用「Install a Local Fax Printer」選單將它加回來,但 Windows 10 早已大幅改版無此選項。 所以,兩台機器不管有無 Fax 印表機都可發傳真是事實,真相成謎,暫且歸入 X 檔案。


非網站 Windows 之 SSL 加密弱點檢測及修補

$
0
0

資安領域深似海,弱點掃描通常是由資安人員或廠商執行,跑工具程式出報告,再依報告進行修補。說起來有點像人體做健檢,但差在拿到的是用火星文寫的健檢報告,隔行如隔山,天曉得怎麼改善? 試想如果你的健檢報告出現一條紅字「TMD 指數低下,免疫力不足,感染 S95 病毒風險偏高」,沒人跟你解釋要怎麼治療,也沒家醫科可以掛號,然後咧?

這回拿到弱點掃描報告有一條:

SSL Medium Strength Cipher Suites Supported
The remote host supports the use of SSL ciphers that offer medium strength encryption. Nessus regards medium strength as any encryption that uses key lengths at least 64 bits and less than 112 bits, or else that uses the 3DES encryption suite.
Note that it is considerably easier to circumvent medium strength encryption if the attacker is on the same physical network.

大意是遠端系統允許使用金鑰長度不夠的 SSL 加密方式(Cipher),防護強度不足有被破解偷窺的風險。參考

乖乖爬文找解藥吧! (補聲暗)

找到一篇文章 讓你的 SSL 更安全 – 移除弱 SSL 加密方式 (Cipher) – I T 練肖喂解釋得挺清楚。不過,我最大疑問是 - 被舉報有問題的幾台機器根本沒開啟 HTTPS,有些甚至連 IIS 都沒裝,為什麼會被挑剔 SSL 加密強度不夠?

再多查一些資料,我才知道除了 IIS,遠端桌面(RDP, Port 3389)也會使用 SSL 加密。

資安人員建議的做法是修改完等固定排程統一重掃,隔天可以看報告有沒有修好。這豈不回到了卡片打孔跑程式的時代,試完不能馬上看結果,身為現代王藍田,我哪受得了?

因此,我想要一個能立即檢測 SSL 弱點的掃描工具,即時回饋問題是否修復,不然多虐心。網路介紹的 SSL 弱點掃描工具多半針對 HTTPS 網站應用,像最多人推的 SSLScan似乎就只能用來檢查網站。(如有錯請指正)

接著,我學到來自 LINUX 世界的強大網路掃描工具 - nmap (簡易教學:Nmap 網路診斷工具基本使用技巧與教學 - G. T. Wang)。而 nmap 最威能之處是它能透過腳本擴充各式檢查,其中 ssl-enum-ciphers可列舉遠端主機支援的 SSL 加密方法,支援協定包含 RDP 3389,就是我在尋覓的利器!

使用方法如下,安裝後執行 nmap -p 3389 --script ssl-enum-ciphers ip_address 程式會列出該主機 RDP 服務所支援的所有 SSL Cipher。

以下是修補前的檢查結果:

C:\Program Files (x86)\Nmap>nmap -p 3389 --script ssl-enum-ciphers 192.168.35.7
Starting Nmap 7.70 ( https://nmap.org ) at 2018-05-16 11:41 ¥x¥_?D·CRE?!
Nmap scan report for 192.168.35.7
Host is up (0.0045s latency).

PORT     STATE SERVICE
3389/tcp open  ms-wbt-server
| ssl-enum-ciphers:
|   TLSv1.1:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
|       TLS_RSA_WITH_RC4_128_SHA (rsa 2048) - C
|       TLS_RSA_WITH_RC4_128_MD5 (rsa 2048) - C
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       64-bit block cipher 3DES vulnerable to SWEET32 attack
|       Broken cipher RC4 is deprecated by RFC 7465
|       Ciphersuite uses MD5 for message integrity
|       Weak certificate signature: SHA1
|   TLSv1.2:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (dh 1024) - A
|       TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (dh 1024) - A
|       TLS_RSA_WITH_AES_256_GCM_SHA384 (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_GCM_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
|       TLS_RSA_WITH_RC4_128_SHA (rsa 2048) - C
|       TLS_RSA_WITH_RC4_128_MD5 (rsa 2048) - C
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       64-bit block cipher 3DES vulnerable to SWEET32 attack
|       Broken cipher RC4 is deprecated by RFC 7465
|       Ciphersuite uses MD5 for message integrity
|       Key exchange (dh 1024) of lower strength than certificate key
|       Weak certificate signature: SHA1
|_  least strength: C

Nmap done: 1 IP address (1 host up) scanned in 3.97 seconds

結果顯示,目前該主機支援的加密方法中共有

TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048)
TLS_RSA_WITH_RC4_128_SHA (rsa 2048)
TLS_RSA_WITH_RC4_128_MD5

三項被列為 C 級,而這就是問題主機被舉發 SSL 加密強度不足的來源。

要改善此一弱點,最有效的做法是透過修改 Registry 停用強度不足的 Cipher。SSL Cipher 相關 Regisry 的官方說明在 Transport Layer Security (TLS) registry settings - Microsoft Docs,若嫌官方文件太長,可以參考這篇 How to disable RC4 and 3DES on Windows Server-

依照文章,我停用了長度為 40,56,128 的 RC4 以及 3DES 四種 Cipher。

停用後,重跑 nmap 可以發現三項等級 C 的弱點消失了,伺服器的 SSL 加密等級升到 A 級,也通過了廠商弱描工具的檢測。(灑花)

C:\Program Files (x86)\Nmap>nmap -p 3389 --script ssl-enum-ciphers 192.168.35.7
Starting Nmap 7.70 ( https://nmap.org ) at 2018-05-16 11:52 ¥x¥_?D·CRE?!
Nmap scan report for 192.168.35.7
Host is up (0.0010s latency).

PORT     STATE SERVICE
3389/tcp open  ms-wbt-server
| ssl-enum-ciphers:
|   TLSv1.1:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       Weak certificate signature: SHA1
|   TLSv1.2:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (dh 1024) - A
|       TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (dh 1024) - A
|       TLS_RSA_WITH_AES_256_GCM_SHA384 (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_GCM_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       Key exchange (dh 1024) of lower strength than certificate key
|       Weak certificate signature: SHA1
|_  least strength: A

Nmap done: 1 IP address (1 host up) scanned in 3.55 seconds

長知識了。

土砲版焊接小幫手

$
0
0

看到一篇 8 歲香港小妹妹在 YouTube 分享電子小玩意 DIY 的報導,影片裡的道具很搶眼:


照片來源:專訪 - 香港 8 歲小妹妹 上網教人 DIY 焊接電路 - 香港 UNWIRE.HK 玩生活.樂科技

四支前端裝有夾子像異形觸手的可彎曲塑膠管,夾住電路板喬好角度,雙手專心拿烙鐵跟焊錫就好,這是何等優雅從容呀! 想到我焊接線路常搞到大粒汗小粒汗,雙手要騰出無名指跟小指扶住電路板,物件尺寸較大或角度刁鑽時如同手指做瑜珈,時間久了還會顫抖發麻,超狼狽的。工欲善其事,必先利其器,小妹妹都知道要用專業裝備,我還在用木棍石頭打獵,像話嗎?

爬文查到這道具有個可愛的名字叫「焊接小幫手」 - 【好物開箱】DIY焊接工具 - 進階實用組 (下篇) - Building Maker Economy:自造達人社群-媒體-平台,台灣有售但價格不斐。那,不如自己做一組吧~

要準備的材料有:

  • 萬向曲管 / 萬向噴油管 2分管(1/4吋) 28公分長(比鄉民略短) 45元/支 (購自網拍)
    這玩意原本用在車床鑽床給水給油噴氣, 角度調整幅度大又可固定不位移,除了造型不賞心悅目,可應用的場合蠻多的。
  • 歐洲夾 5元/支 (光華商場電子材料行)
    歐洲夾較細長,比鱷魚夾好施力,長柄為圓柱狀,材料行的歐洲夾有兩種尺寸,較大的那一段與我買的萬向曲管噴嘴口徑差不多相等,直接插入固定連膠水都省了,呵。
  • 熱縮套管 12元/1米 (光華商場電子材料行)
    用來套在歐洲夾口,防止尖銳鋸齒劃傷物體表面。
  • 基座木板
    原本打算去特力屋切一塊便宜拼接板,意外在路邊拾荒撿到一塊合用的,嘿...
  • 曲管固定座
    3D列印,自製無價
  • 攻牙螺絲 0.5元/根 (五金行都有,我在光南百貨買到,呵)

最麻煩的部分是木板加工,2 分管比想像中粗,工具箱裡最大號鑽頭仍不夠,最後我犠牲了一個大小相符的內六角套筒頭,用銼刀在筒口磨出鋸齒權充鑽頭,硬生生將鑽孔擴大到口徑一致。

只靠插洞固定效果不佳,促成我第一次體驗「缺什麼零件自己印」的暢快。Autodesk 的免費 3D 建模工具 - Tinkercad,非常易學好上手,我設計了一個內六角孔固定器,兩側附有螺絲孔,負責將曲管緊緊固定在木板上。

PLA 料質印成零件強度挺好,精密度也夠,與管身六角螺帽密合良好,鎖上螺絲後的固定效果比預期好很多。

組裝成品如下。我還多預留兩個孔,未來可擴充到六隻手~

多了額外四隻手,未來焊接就能三頭六臂如有神助囉! (灑花)

Email 客戶端之 CSS 支援問題

$
0
0

活到老學到老,今天又學會一件事。

雖然顯示 HTML 格式已是當今 Email 軟體或線上信箱的必要條件,但許多被視為基本的 CSS 功能卻不一定在支援範圍內。

用以下範例展示,我設計一段 HTML 當作 Email 內文。先將 .dynamic 設成 display: none,再指定 .mode-1 .dynamic.mode-1 及 .mode-2 .dynamic.mode-2 為 display: inline,如此在容器加 class="mode-1" 或 class="mode-2" 可切換顯示不同區塊內容;下方 <div> 則示範 overflow: hidden 效果。

<style>
	.dynamic { display: none; }
	.mode-1 .dynamic.mode-1 { display: inline; }
	.mode-2 .dynamic.mode-2 { display: inline; }</style><div class="mode-1"><span class="dynamic mode-1">Show on Mode 1</span><span class="dynamic mode-2">Show on Mode 2</span></div><div style="width:200px;overflow:hidden;border:1px solid red;"><div style="width:480px;background-color:yellow;">
		Overflow Hidden Test</div></div>

裡面用到的都是很基本的 CSS 技巧,感覺不應會有問題。不過,當成信件內文寄到 Outlook 卻面目全非。mode-1 / mode-2 切換文字沒出現,設了寬度及 overflow: hidden 的 <div> 被內層 <div> 撐開。

Outlook 介面上有個「如果這個訊息的顯示有任何問題,請按一下這裡,在瀏覽器中檢視。」,點選改用 IE 開啟,這才呈現它應有的樣貌。

由此我學到一件事,Outlook 雖然支援 HTML 格式,但只支援部分的 CSS 功能!

基於安全考量不允許 JavaScript 我能理解,但不支援一些很基本的 CSS 倒在意料之外(猜想是為了簡化程式複雜度及效能考量)。

爬文找到一個很棒的參考資源: Email Client CSS Support - Email Design Reference

其中整理 Gmail、Outlook.com、Yahoo! Mail、Outlook、Apple Mail、iOS、Android 等各家 Email 客戶端對各式 CSS 特性的支援程度,大家在設計信件 Email HTML 內容記得參考,以免 HTML 在客戶端走針而不自知。

Chrome 記憶密碼誤填欄位問題

$
0
0

同事報案,Chrome 會莫名把客戶帳號填入輸入與帳號無關的 <input type="text"> 欄位,初步研判是 Chrome 內建的帳號密碼記憶小工具 Google Smart Lock搞鬼。

用以下網頁重現問題。如操作所示,在登入網頁 Logon.aspx 用 Google Smart Lock 記下密碼,登入後導向 Index.html,網頁上只有一個訂單編號,Chrome 卻自動自發填上使用者名稱。

原因在於 Index.html 中有個被 display: none 隱藏的 <input type="password" />,測試發現當 Chrome 偵測到網頁有 <input type="password" >(即使它被隱藏),就會判定為登入頁面試著找出帳號密碼欄位填入內容,只要隸屬同一 Domain,不受限當初記憶密碼所在 URL 或欄位 id、name,Chrome 都會努力填上資料。

<html><head><title>Query</title></head><body><div>
Symbol: <input type="text" > <input type="submit" value="Query" /></div><div style="display:none">
CheckKey: <input type="password"></div></body></html>

爬文找到 2008 就有人舉報這是個 Bug :1854 - The username and password remember option fill up non login fields. - chromium – Monorail,例如用 <intput type="password"> 輸入信用卡號時會誤擊。開發團隊認為以 Domain 為單位且不限定欄位名稱,有助於廣泛滿足各式情境,故無計劃修正。(但加註了 autocomplete="off" 還被填入內容則是 Bug 無誤)

既然這是 Feature 不是 Bug,來看看該怎麼修改避免問題。

依據 Stackoverflow 的這篇討論

  • Look up to find an upper input type text from the first input type password (Not a hiddentype nor disabled) to pick it as username

Chrome 會將 <input type="password"> 上方非 hidden 或 disabled 的 <input type="text"> 當成使用者名稱欄位(實測 <input style="display: none"> 也會被排除)。我想到的解法是仿效戰機被飛彈鎖定時放出錫箔絲( Chaff )或熱焰彈(Flare)干擾追蹤(想起小時候看飛狼會吐神奇熱導體,什麼飛彈都能閃),在網頁最頂端放上兩個無用 <input type="text">及<input type="password"> 引誘 Google Smart Lock 攻擊填入資料,避免干擾正常欄位,再用<div style="position:absolute;top:-100px">包覆使之隱形。像這個樣子:

<html><head><title>Query</title></head><body><div style="position:absolute;top:-100px"><input type="text" title="Chaff for Chrome Smart Lock" /><input type="password" title="Chaff for Chrome Smart Lock" /></div><div>
Order No: <input type="text" > <input type="submit" value="Query" /></div><div style="display:none">
CheckKey: <input type="password"></div></body></html>

實測可行,僅供大家參考。

OpenCC 中文繁簡體轉換工具

$
0
0

漫長的碼農生涯,難免會遇到中文繁簡轉換需求,過去我都依賴 Word,但在 Web Server 整合 Word 是件麻煩事。Word 程序體積龐大,啟動要耗用不少記憶體跟 CPU,不適合每次 Request 隨用隨建用完即丟。加上 Word 為桌面程式會綁執行身分,不適合用 IIS AppPool 帳號跑。最後我琢磨出來的解決方案是寫成 Windows Service 以 WebAPI 方式提供服務,服務啟動時開啟固定數量的 Word 程序,分攤處理需求。在實務經驗中,Word 偶爾會因不明原因故障,故得加上連續出錯就重啟 Word 的自我修復機制。另外,伺服器得安裝 Office 多少也增加部署複雜度。

總之,利用 Word 做繁簡翻譯稱不上是完美解決方案。

另一個評估過的繁簡轉換選項是 Microsoft Visual Studio International Pack 1.0 SR1,它有 NuGet套件可直接下載安裝,只需一顆 ChineseConvert.dll 就搞定很方便,可惜不支援字彙轉換,例如「預設記憶體大小及硬碟容量」 應翻成「缺省内存大小及硬盘容量」,幾乎都會被客戶打槍。

最近(事實上 Lag 很久了),我發現一個優秀開源專案 OpenCC https://github.com/BYVoid/OpenCC ( 線上展示 ) ,作者 BYVoid是神人 (請參見網路流傳作者的阿里巴巴面試評語 )。OpenCC 以 C++ 開發,支援 Linux、Mac OS X、Windows、iOS、Android 等平台,官方可下載的已編譯 Windows 版只到 1.0.1 版,1.0.2 版之後的新版需自行編譯,而最新版為 1.0.5 (2017/2/6)。

如果你想自己編譯 1.0.5 版,可以參考這篇:实战Windows下编译Opencc 1.0.5 - CSDN博客,該文章作者有提供編譯好的 1.0.5 Windows 版本,但需要CSDN積分才能下載。 最後我決自己試著用  VS2015 編譯,先安裝 CMake再照著官方文件的 Windows Visual Studio 2013 or higher 編譯步驟:

cmake -H. -Bbuild -G"Visual Studio 14 Win64" -DCMAKE_INSTALL_PREFIX="path/to/install" 
cmake --build build --config Release --target install

身為 C++麻瓜,原本只抱著姑且一試的心態,但 Github 上最新版本已排除大部分會遇到的問題,只有一個小地方要調。原本編譯有錯,我參考前述編譯指南將 PhraseExtract.cpp 改為 Unicode (UCS-2 Little Endian) 就成功了。

編譯輸出目錄 path/to/install/share/opencc 下有字典檔及設定檔,執行檔則在原始碼目錄下的 build\src\tools\Release 資料夾,共有 opencc.dll、opencc.exe、opencc_dict.exe、opencc_phrase_extract.exe 四個檔案,將兩個目錄合併即可使用。

如果嫌自己編譯麻煩,直接下載使用 1.0.1 版也是可以的,1.0.1 版應已能滿足需求。版本比較資訊

將繁體文字檔轉成簡體的指令為:

opencc -i 繁體中文檔案路徑 -o 簡體中文檔案路徑 -c tw2sp.json

其中 -c 指定轉換設定檔,OpenCC 支援以下幾種轉換:

  • s2t.json Simplified Chinese to Traditional Chinese 簡體到繁體
  • t2s.json Traditional Chinese to Simplified Chinese 繁體到簡體
  • s2tw.json Simplified Chinese to Traditional Chinese (Taiwan Standard) 簡體到臺灣正體
  • tw2s.json Traditional Chinese (Taiwan Standard) to Simplified Chinese 臺灣正體到簡體
  • s2hk.json Simplified Chinese to Traditional Chinese (Hong Kong Standard) 簡體到香港繁體(香港小學學習字詞表標準)
  • hk2s.json Traditional Chinese (Hong Kong Standard) to Simplified Chinese 香港繁體(香港小學學習字詞表標準)到簡體
  • s2twp.json Simplified Chinese to Traditional Chinese (Taiwan Standard) with Taiwanese idiom 簡體到繁體(臺灣正體標準)並轉換爲臺灣常用詞彙
  • tw2sp.json Traditional Chinese (Taiwan Standard) to Simplified Chinese with Mainland Chinese idiom 繁體(臺灣正體標準)到簡體並轉換爲中國大陸常用詞彙
  • t2tw.json Traditional Chinese (OpenCC Standard) to Taiwan Standard 繁體(OpenCC 標準)到臺灣正體
  • t2hk.json Traditional Chinese (OpenCC Standard) to Hong Kong Standard 繁體(OpenCC 標準)到香港繁體(香港小學學習字詞表標準)

建議使用tw2sp.json,才有常用字彙轉換效果,實測如下,

如此,我們找到速度比呼叫 Word 快 N 倍又支援字彙轉換的理想解決方案,下篇文章,來談談如何透過 .NET 呼叫整合。

Viewing all 2311 articles
Browse latest View live