Edited at

libcurlでurlencodeしたい

More than 1 year has passed since last update.

手元で urlencode/urldecode するのに毎回rubyとかでワンライナー書かなきゃならないのがめんどくさくて、

どうせ libcurl 入ってるんだからもう標準っぽいコマンドが欲しいなー、とかずっと思ってたけど、

じゃあもう簡易ラッパ書くか、っていう話。



  • cgo 触りたかったのと、

  • 中身は libcurl の共有ライブラリを参照して、ガワの部分はクロスコンパイルしたい目論見で、

golang で書いてみたはいいものの…

後述する問題のためにクロスコンパイルが実現できず、そしたらもう全部cでいいじゃん状態。

gccのFLAG類がコメントに含められるので Makefile 書かなくていい分ちょっと楽かも。

curl のヘッダが要るので、カレントディレクトリにcloneしておく。

あと、 LDFLAGSlibcurl のあるパスに書き換えが必要。


urlencode


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


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)
}



ビルドと実行

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++にした。


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;
}



ビルドと実行

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なので、こう。

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

linuxならlddで。