C++(Arduino)で簡単なURLエンコード/デコードを実装する

Arduinoでネットワーク通信する際に必要だったのですが、どれも凝った実装ばかりで、ブラックボックスなままコピペするのも癪だったので、勉強も兼ねて簡単な実装をしてみました。

日本語には対応してません、悪しからず。


# テーブル
エンコード/デコードを行う際の、文字コード識別テーブルを用意します。


### エンコード用
```c++:UrlEncodeTable
const char encTable[16] = {
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
```


### エンコード対象識別用
```c++:UrlSafeTable
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
};
```


### デコード用
```c++:UrlDecodeTable
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
};
```


# エンコーダ/デコーダ
### エンコーダ
```c++:UrlEncoder
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)`の場合
1. `safeTable[0x23] == 0`の為、変換対象
2. `0x02`と`0x03`に分割
3. `encTable[0x02] == '2'`
4. `encTable[0x03] == '3'`
5. 出力は`%23`となる


### デコーダ
```c++:UrlDecoder
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. `%`の1個先は`'2'`
2. `decTable['2'] == 2 ('2' == 0x32)`
3. `2`を4ビット左シフトすると`0x20`
4. `%`の2個先は`'3'`
5. `decTable['3'] == 3 ('3' == 0x33)`
6. `0x20 + 3 == 0x23`となる


# まとめ
普段は便利なライブラリに頼りがちですが、こうして自力で実装してみると、こんな短いコードでも勉強になりました。


余談: `'\0'`とは
`char*`型の配列は、自身の配列終了を識別する為、暗黙的に配列末尾へ`\0`が付与されます。
ポインタを増やして行き、ポインタが指すアドレスの中身が`\0`ならば、そこで配列は終了(文字列終了)と判別可能なのです。


# SpecialThanks!
- [URI Encoding and Decoding - codeguru](https://www.codeguru.com/cpp/cpp/algorithms/strings/article.php/c12759/URI-Encoding-and-Decoding.htm)