libcurlでurlencodeしたい

手元で `urlencode`/`urldecode` するのに毎回rubyとかでワンライナー書かなきゃならないのがめんどくさくて、
どうせ　`libcurl` 入ってるんだからもう標準っぽいコマンドが欲しいなー、とかずっと思ってたけど、
じゃあもう簡易ラッパ書くか、っていう話。


* `cgo` 触りたかったのと、
* 中身は `libcurl` の共有ライブラリを参照して、ガワの部分はクロスコンパイルしたい目論見で、

`golang` で書いてみたはいいものの…
後述する問題のためにクロスコンパイルが実現できず、そしたらもう全部cでいいじゃん状態。
gccのFLAG類がコメントに含められるので `Makefile` 書かなくていい分ちょっと楽かも。

[curl](https://github.com/curl/curl) のヘッダが要るので、カレントディレクトリにcloneしておく。
あと、 `LDFLAGS` は `libcurl` のあるパスに書き換えが必要。

## urlencode

```go:urlencode.go
package main

/*
#cgo CFLAGS: -I./curl/include/
#cgo LDFLAGS: -L/usr/local/Cellar/curl/7.54.1/lib/ -lcurl
#include <string.h>
#include <stdio.h>
#include <curl/curl.h>

void escape(const char* buf) {
    CURL* curl = curl_easy_init();
    if(curl) {
        char* output = curl_easy_escape(curl, buf, strlen(buf));
        if(output) {
            fprintf(stdout, "%s", output);
            fflush(stdout);
            curl_free(output);
        }
        curl_easy_cleanup(curl);
    }
}
*/
import "C"

import (
	"io/ioutil"
	"os"
)

func main() {
	buf, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		panic(err)
	}

	s2 := C.CString(string(buf))
	C.escape(s2)
}
```

## urldecode

```go:urldecode.go
package main

/*
#cgo CFLAGS: -I./curl/include/
#cgo LDFLAGS: -L/usr/local/Cellar/curl/7.54.1/lib/ -lcurl
#include <string.h>
#include <stdio.h>
#include <curl/curl.h>

void unescape(const char* buf) {
    CURL* curl = curl_easy_init();
    if(curl) {
        int outlength;
        char* output = curl_easy_unescape(curl, buf, strlen(buf), &outlength);
        if(output) {
            fprintf(stdout, "%s", output);
            fflush(stdout);
            curl_free(output);
        }
        curl_easy_cleanup(curl);
    }
}
*/
import "C"

import (
	"io/ioutil"
	"os"
)

func main() {
	buf, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		panic(err)
	}

	s2 := C.CString(string(buf))
	C.unescape(s2)
}
```

## ビルドと実行

```sh
go build urlencode.go
go build urldecode.go

echo 'Qiitaは、プログラミングに関する知識を記録・共有するためのサービスです。' | ./urlencode | ./urldecode
```

## 余談

超付け焼き刃なので `cgo` の理解が追いついてないのだけど、
~~`go build` すると静的リンクになってしまうけど、本体だけのビルドってできるのかな？~~

デバッグ用シンボルっぽいいろいろ余計なものが入るからサイズがでかくなってるけど、共有ライブラリ使用のビルド自体はできている様子。
というかmacのgccはそもそも `-static` リンクができないっぽい。

```
$ export PKG_CONFIG_PATH=/usr/local/Cellar/curl/7.54.1/lib/pkgconfig
$ g++ -static urlencode.cpp `pkg-config --static --cflags libcurl` `pkg-config --static --libs libcurl`
ld: library not found for -lcrt0.o
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```

ただし、

```
can't load package: package main: build constraints exclude all Go files in /path/to
```

とか出てクロスコンパイルはできない。

## 【おまけ】 c++ で `libcurl.so` 版

そしたらもう全部cでいいじゃん版。

考えてみたらコマンド分ける意味なかったので、`base64` みたいにdecodeオプションにした。
あと、 `stdin` 一気読みがcだと面倒なのでc++にした。

```cpp:urlencode.cpp
#include <string>
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <curl/curl.h>

void escape(const std::string& buf) {
    CURL* curl = curl_easy_init();
    if(curl) {
        char* output = curl_easy_escape(curl, buf.c_str(), buf.size());
        if(output) {
            std::cout << output << std::flush;
            curl_free(output);
        }
        curl_easy_cleanup(curl);
    }
}

void unescape(const std::string& buf) {
    CURL* curl = curl_easy_init();
    if(curl) {
        int outlength;
        char* output = curl_easy_unescape(curl, buf.c_str(), buf.size(), &outlength);
        if(output) {
            std::cout << output << std::flush;
            curl_free(output);
        }
        curl_easy_cleanup(curl);
    }
}

int main(int argc, char* argv[]) {
    int opt;
    bool decode = false;

    while ((opt = getopt(argc, argv, "d")) != -1) {
        switch (opt) {
        case 'd':
            decode = true;
            break;
        }
    }

    std::stringstream sstream;
    sstream << std::cin.rdbuf();

    if(decode) {
        unescape(sstream.str());
    } else {
        escape(sstream.str());
    }

    return 0;
}
```

### ビルドと実行

```sh
CFLAGS=-I./curl/include/
LDFLAGS=/usr/local/Cellar/curl/7.54.1/lib/
g++ urlencode.cpp -o urlencode -lcurl

echo 'Qiitaは、プログラミングに関する知識を記録・共有するためのサービスです。' | ./urlencode | ./urlencode -d
```

### 確認

手元のはmacなので、こう。

```sh
otool -l urlencode | grep --before 3 --after 3 libcurl
```

linuxならlddで。