Arduinoでネットワーク通信する際に必要だったのですが、どれも凝った実装ばかりで、ブラックボックスなままコピペするのも癪だったので、勉強も兼ねて簡単な実装をしてみました。
日本語には対応してません、悪しからず。
テーブル
エンコード/デコードを行う際の、文字コード識別テーブルを用意します。
エンコード用
const char encTable[16] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
エンコード対象識別用
const char safeTable[128] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
/* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 1 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 2 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 3 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
/* 4 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 5 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
/* 6 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 7 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
};
デコード用
const char decTable[128] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
/* 0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 1 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 2 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 3 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
/* 4 */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 5 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 6 */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
/* 7 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
エンコーダ/デコーダ
エンコーダ
String urlEncode(const char* msg){
String encoded = "";
while(*msg != '\0'){
if(safeTable[*msg]){
encoded += *msg;
}
else{
encoded += '%';
encoded += encTable[*msg >> 0x04];
encoded += encTable[*msg & 0x0F];
}
msg++;
}
return encoded;
}
[0-9a-zA-Z]ならば変換は不要なので、そのままencodedへ追加します。
それ以外の文字は、文字コード値の上位4bitと下位4bitをそれぞれテーブルに掛け、16進表記へ変換しています。
例: '#' (0x23)の場合
-
safeTable[0x23] == 0の為、変換対象 -
0x02と0x03に分割 encTable[0x02] == '2'encTable[0x03] == '3'- 出力は
%23となる
デコーダ
String urlDecode(const char* msg){
String decoded = "";
while(*msg != '\0'){
if(*msg == '%'){
char dec1 = decTable[*(msg + 1)];
char dec2 = decTable[*(msg + 2)];
if(dec1 != -1 && dec2 != -1){
decoded += (dec1 << 4) + dec2;
}
msg += 3;
}
else{
decoded += *msg;
msg++;
}
}
return decoded;
}
文字が%でなければ、そのままdecodedへ追加し次の文字へ移ります。
%がヒットしたら、1個先と2個先の文字コード値をそれぞれテーブルに掛け、1個先の値を4ビット左シフト後に2個先の値を足して出力します。
%1zなど、%の先に16進値以外の文字列が来た場合は、何も出力せずスキップします。
例: '#' ("%23")の場合
-
%の1個先は'2' decTable['2'] == 2 ('2' == 0x32)-
2を4ビット左シフトすると0x20 -
%の2個先は'3' decTable['3'] == 3 ('3' == 0x33)-
0x20 + 3 == 0x23となる
まとめ
普段は便利なライブラリに頼りがちですが、こうして自力で実装してみると、こんな短いコードでも勉強になりました。
余談: '\0'とは
char*型の配列は、自身の配列終了を識別する為、暗黙的に配列末尾へ\0が付与されます。
ポインタを増やして行き、ポインタが指すアドレスの中身が\0ならば、そこで配列は終了(文字列終了)と判別可能なのです。