2018年8月27日 星期一

得到圖片原始的真實大小, 檢查input file的upload image 尺寸 - javascript

在這個例子中,展示了如何使用javascript來讀取input type="file" 上傳的圖片尺寸大小

直接上程式:
html :
<input id="imageUploader" type="file" accept="image/*"/>
<br/>
<img id="imageDisplayer" style="width : 300px; height: auto;"/>
Javascript :
var imageUploader = document.getElementById("imageUploader");
imageUploader.addEventListener("change", function(){
 window.URL;
  var image = new Image();
  image.onload = function(){
   var width = image.naturalWidth | image.width;
    var height = image.naturalHeight | image.height;
   console.log("width: " + width + ", height: " + height);
  };
  var objectURL = window.URL.createObjectURL(this.files[0]);
  image.src = objectURL;
  document.getElementById("imageDisplayer").src = objectURL;
});
主要是例用了window.URL.createObjectURL() 來得到上傳圖片的暫時url,
並建立一個 Image 物件來接收此 url ,並在 onload callback 裡得到 width 和 height,
#imageUploader下面那個#imageDisplayer只是預覽用,可有可無。
其中 Image 物件的 naturalWidth 和 natural Height 為上傳圖片的原始尺寸,
但怕有的瀏覽器沒有支援,所以在沒有支援時使用舊的 width, height 屬性

以下為上例程式碼的 jsfiddle 版本

關於Windows NTLM 登入 憑證 Header 解析時要注意的地方 (Java 的 byte 轉 int)

NTLM 是 Windows 的一種登入方式,它會要求使用者打入帳號密碼登入,
此時瀏覽器會跳出一個登入的對話框,當使用者登入後,
可以經由解析Authorization得到使用者的username和domain,

以下是參考這篇文
NTLM Authentication in Java
寫出的 Java 程式碼,可以得到username和domain,
要注意的是,在 NTLM Authentication in Java 的程式碼中,其忽略了 username 和 doamin 的 length 或 offset, byte 轉成 int 時會變成負數的可能性,
而在我們的程式碼中對此做出了修正。

String auth = request.getHeader("Authorization");
String szUserName = "";
String szDomain = "";
StringBuffer szUserNameBuffer =  new StringBuffer();
StringBuffer szDomainBuffer =  new StringBuffer(); 
if (auth == null)
{
 response.setStatus(response.SC_UNAUTHORIZED);
 response.setHeader("WWW-Authenticate", "NTLM");
 response.flushBuffer();
 return;
}
if (auth.startsWith("NTLM "))
{
 byte[] msg = new sun.misc.BASE64Decoder().decodeBuffer(auth.substring(5));
 int off = 0, length, offset;
 if (msg[8] == 1)
 {
  byte z = 0;
  byte[] msg1 = {(byte)'N', (byte)'T', (byte)'L', (byte)'M', (byte)'S', (byte)'S', (byte)'P', z,(byte)2, z, z, z, z, z, z, z,(byte)40, z, z, z, (byte)1, (byte)130, z, z,z, (byte)2, (byte)2, (byte)2, z, z, z, z, z, z, z, z, z, z, z, z};
  response.setHeader("WWW-Authenticate", "NTLM " + new sun.misc.BASE64Encoder().encodeBuffer(msg1));
  response.sendError(response.SC_UNAUTHORIZED);
  return;
 }
 else if (msg[8] == 3)
 {
  off = 30;
  
  length = (msg[off+17]&0x0FF)*256 + (msg[off+16]&0x0FF);
  offset = (msg[off+19]&0x0FF)*256 + (msg[off+18]&0x0FF);
  String remoteHost = new String(msg, offset, length);
  
  length = (msg[off+1]&0x0FF)*256 + (msg[off]&0x0FF);
  offset = (msg[off+3]&0x0FF)*256 + (msg[off+2]&0x0FF);
  String domain = new String(msg, offset, length);
  
  length = (msg[off+9]&0x0FF)*256 + (msg[off+8]&0x0FF);
  offset = (msg[off+11]&0x0FF)*256 + (msg[off+10]&0x0FF);
  String username = new String(msg, offset, length);
  
  char[] ca = remoteHost.toCharArray();
  String szHost = "";
     for(int i = 0; i < ca.length; i = i + 2){
      szHost = szHost + (char)ca[i];
     }
  
  char[] ca2 = domain.toCharArray();
     for(int i = 0; i < ca2.length; i = i + 2){
      szDomain = szDomain + (char)ca2[i];
     }
     
     char[] ca3 = username.toCharArray();
     for(int i = 0; i < ca3.length; i = i + 2){
      szUserName = szUserName + (char)ca3[i];
     }
 }
}else if(auth.startsWith("Negotiate")){
 byte[] msg = new sun.misc.BASE64Decoder().decodeBuffer(auth.substring(10));
 int off = 0, length, offset;
 if (msg[8] == 1){
  byte z = 0;
  byte[] msg1 = {(byte)'N', (byte)'T', (byte)'L', (byte)'M', (byte)'S', (byte)'S', (byte)'P', 
    z,(byte)2, z, z, z, z, z, z, z,(byte)40, z, z, z, 
    (byte)1, (byte)130, z, z,z, (byte)2, (byte)2,
    (byte)2, z, z, z, z, z, z, z, z, z, z, z, z};
  response.setHeader("WWW-Authenticate", "NTLM " +  new sun.misc.BASE64Encoder().encodeBuffer(msg1));
  response.sendError(response.SC_UNAUTHORIZED);
     return;
 }else if (msg[8] == 3){
  off = 30;

  length = (msg[off+17]&0x0FF)*256 + (msg[off+16]&0x0FF);
  offset = (msg[off+19]&0x0FF)*256 + (msg[off+18]&0x0FF);
  // String remoteHost = new String(msg, offset, length);

  length = (msg[off+1]&0x0FF)*256 + (msg[off]&0x0FF);
  offset = (msg[off+3]&0x0FF)*256 + (msg[off+2]&0x0FF);
  szDomain = new String(msg, offset, length);

  length = (msg[off+9]&0x0FF)*256 + (msg[off+8]&0x0FF);
  offset = (msg[off+11]&0x0FF)*256 + (msg[off+10]&0x0FF);

  szUserName = new String(msg, offset, length , "UTF-8" );
 }
}

String charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_".toLowerCase();
for(int i=0; i<szUserName.length();i++){
 if(charSet.contains(String.valueOf(szUserName.charAt(i)).toLowerCase())){  
  szUserNameBuffer.append(szUserName.charAt(i));
 }
}
for(int i=0; i<szDomain.length();i++){
 if(charSet.contains(String.valueOf(szDomain.charAt(i)).toLowerCase())){  
  szDomainBuffer.append(szDomain.charAt(i));
 }
}


修正的方式很簡單,就是將byte與 0x0FF 進行和(&) 運算
那麼為什麼會需要這樣的修正呢?
我們可以看看 NTLM Header 的 Schema,在 NTLM Authentication Scheme for HTT 中
有說明到
" byte is an 8-bit field; short is a 16-bit field. All fields are unsigned. Numbers are stored in little-endian order. "

以domain length 為例,表示了其是用 short 16-bit 來儲存,
並且是用 little-endian order,即後8-bit 為高位,
最後為unsigned的,也就是一定為正數,即使8-bit中的最高位為1也是一樣為正。

如果在 8-bit 中的最高位為0, byte 轉 int 是沒問題的,
但是如果最高位為1的話,直接轉就會出問題了,
例如: 144 (NTLM- unsigned 10進位)
1001 0000 (byte) 在轉成 int 時,因為 Java 看到最高位為1,誤以為是負數,且 int 為 32-bit,所以就在前面多出的24個位元補上了1,而變成

1111 1111 1111 1111 1111 1111 1001 0000 (int) ==> -112 (Java signed 10 進位)

這樣就出錯了,
很顯然,前面的24個位元應該都要為0才對,所以我們只要在對其與 0xFF進行和(&)運算,
將前面24個位元都變成0就行了。

0000 0000 0000 0000 0000 0000 1001 0000 (int) ==> 144 (NTLD unsigned 10 進位)

參考資料:

  1. NTLM Authentication Scheme for HTT
  2. NTLM Authentication in Java (此處程式碼不夠好,沒有考慮到本文錯誤取到負值的狀況)
  3. java中byte轉換int時為何與0xff進行與運算
  4. Base64



2018年8月4日 星期六

IIS httpd.ini處理url有帶?加參數的情況

IIS 的轉址設定檔httpd.ini中,可以對特定的Url轉換成另一種Url,但如果要被轉的Url是有帶參數的網址,我們想把參數加在轉換後的Url後面,要如何做呢?

在這裡介紹一個可用的方法

首先例如有一下轉換設定:

RewriteRule /url-(.*?).html     /url.jsp?id=$1

可以進行例如以下url的轉換
/url-123.html     ==>     /url.jsp?id=123

但是如果是有帶參數的url就無法成功匹配轉換規則了
/url-123.html?param=abc    ==>     ???

有兩種解法,

一種是多加一條規則去處理有帶參數的情況:
RewriteRule /url-(.*?).html?(.*?)     /url.jsp?id=$1&$2
這樣就可成功轉換,例如以下:
/url-123.html?param=abc     ==>     /url.jsp?id=123&param=abc
但是這樣需要設定兩條規則:
RewriteRule /url-(.*?).html     /url.jsp?id=$1
RewriteRule /url-(.*?).html?(.*?)     /url.jsp?id=$1&$2

另一種寫法只要設定一條規則就行了,規則如下:
RewriteRule /url-(.*?).html(\?.*?)?     /url.jsp(?2$2&:?)id=$1
此寫法可以做到以下url轉換:
/url-123.html     ==>     /url.jsp?id=123
/url-123.html?param=abc     ==>     /url.jsp?param=abc&id=123

接下來來講解第二種方法的原理,
其用到了類似很多程式都有的"三元運算子"規能,規則如下:
(?NtrueExpression:falseExpression)
當有抓到第N匹配群組時,執行trueExpression,沒有時則執行falseEpression,
所以
/url.jsp(?2$2&:?)id=$1
的意恩就是當有抓到2匹配群組時,執行"$2&",否則則執行"?",
而$2就是抓到的url參數部份
在這裡
/url-(.*?).html(\?.*?)?
中的(\?.*?)?   即是代表第2群組  (\?.*?)  出現的次數可為0次或1次,

所以當出現為1次時,就把抓到的參數(連同?),例如上例的 ?param=abc,
放到 /url.jsp的後面再加個 &,而id=$1就放在&的後面,

如果出現為0次時,就直接再 /url.jsp的後面加個 ? ,再把id=$1放在 ? 的後面

參考資料:

  1. ISAPI_Rewrite 2 documentation  的 "Conditional expressions" 部份
  2. Topic: Query String and IIS error msg