[Python] 338 ディレクトリ内ファイルの文字コード一括変換 上書きの場合

[M1 Mac, Big Sur 11.6.8, Python 3.10.4]

Pythonに関する記事は約1ヶ月ぶりです。

最近TCP/IPに関する書籍を購入したのですが、サンプルコードの文字コードがEUC-JPなので以下のコードでUTF-8に一括変換しました。

サンプルコードのディレクトリをコピーして上書き変換させています。過去に類似の記事がありますが、別ファイルへ変換したケースです。

import glob,subprocess

dir = "/sample_code/CSockets_copy"
paths_c = glob.glob(dir + "/**/*.c", recursive=True)
paths_h = glob.glob(dir + "/**/*.h", recursive=True)

paths_c.extend(paths_h)

print(paths_c)
print(len(paths_c))

for path in paths_c:
    cmd = "nkf -w --overwrite %s" %path
    subprocess.call(cmd, shell=True)

[C++] 164 POP3プロトコル POP3サーバの仮設置 Dovecot

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

『C言語によるTCP/IPネットワークプログラミング』(2001年) 第3章はPOP3プロトコルです。Intel MacからM1 Macに戻ってきました。

DovecotというIMAP/POP3サーバを導入し、プログラムで接続するところまで進めました。これはかなり難易度が高かったです。朝から深夜まで休憩をはさみつつ丸一日掛かっています。実験環境構築に没頭するあまり、目指しているわけでもないブログの連続投稿が途切れました。

まずHomebrewからインストールしたDovecotは起動してもポートを開けられませんでした。次にソースコード(v 2.3.19.1)をビルドしようとしたところmakeで失敗。Intel Macでもダメでした。エラー内容を確認するとコンパイルオプション-std=gnu99が悪さをしているようです。

2019年8月リリースの安定版 v 2.2.36.4をビルドするとこれが上手くいき、そのまま接続までスムーズでした。M1 Mac発売から1年以上前のコードが動くとは正直思いませんでした。

こちらのMakefileには-std=gnu99が入っていません。最新版は古いコード仕様も使えるようにしてバグが発生したということでしょうか。

話は変わりますが、Homebrewはこれまでの経験から今一つ信用できないところがあります。Homebrew版が使えなくて、GitHubから入手したコードで成功した例が結構あります。意地でもやらないでしょうが、RubyにこだわらずPythonも開発言語に加えてはいかがでしょうか。

とにかく接続はできたので、あとはコードの詳細を詰めていきます。

DovecotによりPOP3のポート番号110開放(netstat -natコマンドで確認)
poptestコマンドで接続成功

[C++] 163 SMTPプロトコル 仮想SMTPサーバーとの通信

[Intel Mac, Big Sur 11.6.5, clang 13.0.0, NO IDE]

『C言語によるTCP/IPネットワークプログラミング』(2001年)第2章 SMTPプロトコルの所を学習しています。

仮想SMTPサーバー FakeSMTPにメールを送信しデータのやり取りをさせました。

サンプルコードにあったEUC-JP→ISO-2022-JP変換関数とBase64変換関数は大幅に変更しました。EUC-JP→ISO-2022-JP変換(MacはUTF-8から変換)はネットにあったコードを丸々拝借しました。libiconvライブラリを使用します。Base64変換についてはclxライブラリをダウンロードして使っています。これによりcppファイル2つを削減しました。

FakeSMTPのソースコードを入手しM1 Macでビルドしたものの、残念ながら起動しませんでした。

Apple Silicon版仮想SMTPサーバーを自製できれば良いのですが。

#include <mailtest.h>
#include <clx/base64.h>

int main(int argc,char *argv[])
{
	struct passwd	*pwd;
	char	hostname[MAXHOSTNAMELEN+1];
	time_t	t;
	char	from[65];
	char	b_subject[1024];
	char	j_data[1024*2];
	char	*ptr;
	char	buf[1024];
	char	boundary[80];
	char	*f_buf,*b_buf;
	FILE	*fpd;
	struct stat	st;
	int	i;
	int	soc;

	if(argc <= 1){
		fprintf(stderr,"mailtest recpient [file] [file] ...\n");
		return 0;
	}

	 // JIS:Base64のサブジェクトを標準入力から取得
	GetSubject(b_subject);

	 // 本文データを標準入力から取得
	GetData(j_data);

	cout << "GetData pass" << endl;

	 // 送信者設定
	pwd = getpwuid(getuid());
	gethostname(hostname,MAXHOSTNAMELEN);
	sprintf(from,"%s@%s",pwd->pw_name,hostname);

	cout << "from: " << from << endl;

	 // ローカルホストのSMTPに接続
	if((soc = ConnectHost("localhost","smtp",25)) == -1){
		fprintf(stderr,"Cannot connect to local smtp.\n");
		return 0;
	}

	 // SMTPとの通信
	SOCprintf(soc,"HELO %s\r\n",hostname);
	SOCrecv(soc,buf);
	SOCprintf(soc,"MAIL FROM:%s\r\n",from);
	SOCrecv(soc,buf);
	SOCprintf(soc,"RCPT TO:%s\r\n",argv[1]);
	SOCrecv(soc,buf);

	 // データ送信開始
	SOCprintf(soc,"DATA\r\n");
	SOCrecv(soc,buf);

	 // ヘッダ部送信
	t=time(&t);
	SOCprintf(soc,"Date: %s",ctime(&t));
	SOCprintf(soc,"From: %s\r\n",from);
	SOCprintf(soc,"Subject: =?ISO-2022-JP?B?%s?=\r\n",b_subject);
	SOCprintf(soc,"To: %s\r\n",argv[1]);
	SOCprintf(soc,"MIME-Version: 1.0\r\n");
	sprintf(boundary,"%010d_%09d",t,getpid()); // プロセスID取得
	SOCprintf(soc,"Content-Type: MULTIPART/mixed; BOUNDARY=%s\r\n",boundary);
	SOCprintf(soc,"\r\n");

	 // データ部送信
	SOCprintf(soc,"--%s\r\n",boundary);
	SOCprintf(soc,"Content-Type: TEXT/plain; charset=ISO-2022-JP\r\n");
	SOCprintf(soc,"\r\n");

	send(soc,j_data,strlen(j_data),0);

	SOCprintf(soc,"\r\n");

	 // 添付ファイル送信
	for(i = 2; i < argc; i++){
		if((fpd = fopen(argv[i], "r"))!= NULL){
			printf("argv[%d] %s\n",i,argv[i]);

			fstat(fileno(fpd), &st); // ファイル状態を取得
			f_buf=(char *)malloc(st.st_size);
			b_buf=(char *)malloc(st.st_size * 2);
			fread(f_buf, 1, st.st_size, fpd); // ファイルデータを1バイトずつ全サイズ分をf_bufに格納
			fclose(fpd);
			SOCprintf(soc,"--%s\r\n",boundary);
			SOCprintf(soc,"Content-Type: application; name=%s; x-unix-mode=0777\r\n",argv[i]);
			SOCprintf(soc,"Content-Transfer-Encoding: BASE64\r\n");
			SOCprintf(soc,"Content-Description: %s\r\n",argv[i]);
			SOCprintf(soc,"\r\n");
			
			// Base64変換
			string f_buf_str = string(f_buf);
			string f_buf_64 = clx::base64::encode(f_buf_str);
			f_buf_64.copy(b_buf, st.st_size *2 -1);

			// base64(f_buf,st.st_size,b_buf); // base64関数は廃止

			send(soc,b_buf,strlen(b_buf),0);
			SOCprintf(soc,"\r\n");
			SOCprintf(soc,"\r\n");
			free(f_buf);
			free(b_buf);
		}
		else{
			fprintf(stderr,"%s cannot read.\n",argv[i]);
		}
	}

	SOCprintf(soc,"--%s--\r\n",boundary);

	 // データ送信完了 
	SOCprintf(soc,".\r\n");
	SOCrecv(soc,buf);

	 // 終了 
	SOCprintf(soc,"QUIT\r\n");
	SOCrecv(soc,buf);

	 // ソケットクローズ 
	SocketClose(soc);
}

参考サイト

[C++] 162 SMTPプロトコル 仮想SMTPサーバーの設置 Intel Macへの回帰

[Intel Mac, Big Sur 11.6.5, clang 13.0.0, NO IDE]

『C言語によるTCP/IPネットワークプログラミング』(2001年)第2章 SMTPプロトコルのサンプルコードを参考に仮想SMTPサーバーへメールを送る実験に取り組んでいます。

Java製の仮想SMTPサーバー FakeSMTPがM1 Macでは起動できなかったため、Intel Macでの開発に切り替えました。

メールタイトルはUTF-8からISO-2022-JPを経て添付ファイルとともにbase64に変換しました。試行錯誤の末、やっとメールの送信に成功しました。

開発環境は整ったので、これから本腰を入れます。やはりアプリ開発・プログラミング学習をする上でIntel Macはまだまだ必要ですね。

送信したメールの内容
mailtestコマンド(仮送信先と添付ファイル)
--------------------------------------------------
mailtest test_recv@example.com mailtest.h
--------------------------------------------------
出力
--------------------------------------------------
Subject: 日本語
instr 日本語
outstr BF|K\8l // ISO-2022-JP
sub_jis: BF|K\8l
sub_64: GyRCRnxLXDhs // Base64
b_subject: GyRCRnxLXDhs
GetData開始
test
.
instr test

outstr test

GetData pass
from: noMac-mini.local
soc: 3
sockaddr: 0x7ffee0f0b0c8
IP adrress: 0x7ffee0f0b0cc
HELO 
>>>>>220 localhost ESMTP SubEthaSMTP null

MAIL FROM:
>>>>>250 localhost

RCPT TO:
>>>>>250 Ok

DATA
>>>>>250 Ok

Date: From: 
Subject: =?ISO-2022-JP?B??=
To: 
MIME-Version: 1.0
Content-Type: MULTIPART/mixed; BOUNDARY=

--
Content-Type: TEXT/plain; charset=ISO-2022-JP

--
Content-Type: application; name=; x-unix-mode=0777
Content-Transfer-Encoding: BASE64
Content-Description: 

----
.
>>>>>354 End data with <CR><LF>.<CR><LF>

QUIT
>>>>>250 Ok

[C++] 161 SMTPプロトコル ソースファイルの再構成

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

ようやく『C言語によるTCP/IPネットワークプログラミング』の第2章 SMTPプロトコルに進みます。

まずは第1章と同様に2001年作成のソースコードがビルドできるようにしました。cppファイルが3つに増えたので、srcディレクトリなどを作成し、オブジェクトファイルやヘッダファイルを別々に管理するようにしています。

MacOSの場合はそのままではgetuid関数やmalloc関数がないというエラーが出ますが、それぞれunistd.h、stdlib.hをインクルードすれば解決します。

骨が折れる作業ですが、以前手がけたゲームプログラミングの本に比べたら大分楽です。汎用性が低かったゲームのコードに比べ、得られた知識を積み上げてメーラーやサーバーアプリ製作などの成果を上げられそうなのでモチベーションは高いです。

これから動作確認を行います。

getuidに関するMacOSマニュアルの説明
COMPILER = clang++
CPPFLAGS = -g -w -std=c++98

INCLUDE = -I./include
LDLIBS = 

# ソースファイル
SRCDIR = ./src
SRCS = $(shell find $(SRCDIR) -type f)

# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(addprefix $(OBJDIR), $(patsubst ./src/%.cpp,/%.o,$(SRCS)))

# 実行ファイル
TARGETDIR = ./bin
TARGET = mailtest

# cppファイルからoファイル作成 $<:依存ファイル
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LDLIBS)

# 全ソース・コンパイル&ビルド
.PHONY:all
all: clean $(OBJS) $(TARGET)

# oファイル・実行ファイル削除
.PHONY:clean
clean:
	rm -rf $(OBJS) $(TARGETDIR)/$(TARGET)
Smtp $ make all
rm -rf ./obj/base64.o ./obj/mailtest.o ./obj/ujtoj.o ./bin/mailtest
clang++ -g -std=c++98 -w -I./include  -o obj/base64.o -c src/base64.cpp
clang++ -g -std=c++98 -w -I./include  -o obj/mailtest.o -c src/mailtest.cpp
clang++ -g -std=c++98 -w -I./include  -o obj/ujtoj.o -c src/ujtoj.cpp
clang++ -o ./bin/mailtest ./obj/base64.o ./obj/mailtest.o ./obj/ujtoj.o 

[C++] 160 HTTPプロトコル 受信データをファイルに保存

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

『C言語によるTCP/IPネットワークプログラミング』サンプルコード読解の続きです。ここら辺になってくると土台にある知見が膨大なので端折りながら理解に努めます。

受信データのヘッダ部は標準出力し、データ部は同名のファイルとして保存します。

それぞれのデータサイズ算出のため下図にポインタの関係をまとめました。

int SOCrecvDataToFile(int soc, const char *filename)
{
	int	width;
	struct timeval	timeout; // 時刻
	fd_set	readOK;  // FD(ファイルディスクリプタ)
	// fd_set	mask;
	char	tmpbuf[8193]; // 8192 = 2^13
	char	*ptr;
	int	size;
	int end;
	int head;
	FILE	*fp;

	if((fp = fopen(filename, "w")) == NULL){
		perror("fopen");
		return(-1);
	}

	FD_ZERO(&readOK); // FD初期化
	FD_SET(soc,&readOK); // FDセット
	width = soc + 1;

	head =0 ;
	end = 0;
	while(1){
		timeout.tv_sec = 1;
		timeout.tv_usec = 0;
		// readOK = mask;
		// FDを監視
		switch(select(width, &readOK, NULL, NULL ,&timeout)){
			case	-1:
				if(errno!=EINTR){
					perror("select");
					end=1;
				}
				break;
			case	0:
				break;
			default:
				if(FD_ISSET(soc, &readOK)){ // FDを判別
					size = recv(soc, tmpbuf, 8192, 0); // データ受信
					if(size <= 0){
						end = 1;
					}
					else{
						if(head == 0){
							ptr = memstr(tmpbuf, size, "\r\n\r\n", 4); // バイナリ検索
							if(ptr != NULL){
								// ヘッダ部
								fwrite(tmpbuf, ptr-tmpbuf + 4, 1, stdout);
								head=1;
								// データ部 
								fwrite(ptr + 4,size-(ptr-tmpbuf)-4,1,fp);
							}
							else{
								// ヘッダ部
								fwrite(tmpbuf, size, 1, stdout);
							}
						}
						else{
							// データ部 
							fwrite(tmpbuf, size, 1, fp);
						}
					}
				}
				break;
		}
		if(end==1){
			break;
		}
	}

	fclose(fp);

	return(0);
}

[C++] 159 HTTPプロトコル ソケット接続

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

『C言語によるTCP/IPネットワークプログラミング』のサンプルコード読解の続きです。前回のリクエスト送信と順序が逆になってしまいました。

ドメイン名からIPアドレスを取得しソケット接続するConnectHost関数について調べました。コード的に特にいじるところはありませんでした。

// ソケット接続 
int ConnectHost(const char *host,const char *port,int portno)
{
	struct hostent	*servhost;    // ホスト名
	struct servent	*service;     // サービス名
	struct sockaddr_in	sockaddr; // IPアドレス・ポート番号保管
	int	soc;
	int p;

	// ホスト名取得
	if((servhost = gethostbyname(host)) == NULL){ // ホスト名がない場合
		u_long addr;
		addr = inet_addr(host);

		servhost = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET);
		if (servhost == NULL) {
			perror("Error:gethostbyaddr");
			return(-1);
		}
	}

	// サービス名取得
	if((service = getservbyname(port,"tcp")) == NULL){
	}

	// ソケット作成
	if((soc = socket(AF_INET,SOCK_STREAM,0)) < 0){
		perror("socket");
		return(-1);
	}

	// sockaddr作成
	memset((char *) &sockaddr, 0, sizeof(sockaddr));
	sockaddr.sin_family = AF_INET; // アドレスファミリー

	cout << "sockaddrのサイズ(unsigned long) " << sizeof(sockaddr) << endl;

	if(service == NULL){
		if((p = atoi(port)) == 0){
			p = portno;
		}
		sockaddr.sin_port = p;
	} else {
		sockaddr.sin_port = service -> s_port; // ポート番号取得
	}

	// IPアドレスをコピー
	memcpy((char *)&sockaddr.sin_addr,servhost->h_addr,servhost->h_length);

	// servhostアドレスリスト・メモリ番地(このコードでは不要)
	cout << "servhost->h_addr_list " << servhost->h_addr_list << endl;

	// sockaddr・メモリ番地
	cout << "&sockaddr " << &sockaddr << endl;

	// IPアドレス・メモリ番地
	cout << "&sockaddr.sin_addr " << &sockaddr.sin_addr << endl;

	// ソケット接続(ソケット記述子・IPアドレス・ポート番号が必要)
	if(connect(soc,(struct sockaddr *)&sockaddr,sizeof(sockaddr))==-1){
		perror("connect");
		SocketClose(soc);
		return(-1);
	}
	return(soc);
}
host = localhost,path = /index.html
sockaddrのサイズ(unsigned long) 16
servhost->h_addr_list 0x11f8040da
&sockaddr 0x16b681fb8
&sockaddr.sin_addr 0x16b681fbc
request GET /index.html HTTP/1.0

HTTP/1.1 200 OK
Date: Wed, 24 Aug 2022 13:36:26 GMT
Server: Apache/2.4.46 (Unix) OpenSSL/1.0.2u PHP/7.4.21 mod_wsgi/3.5 Python/2.7.18 mod_fastcgi/mod_fastcgi-SNAP-0910052141 mod_perl/2.0.11 Perl/v5.30.1
Last-Modified: Tue, 23 Aug 2022 16:04:38 GMT
ETag: "78-5e6eab8a35180"
Accept-Ranges: bytes
Content-Length: 120
Connection: close
Content-Type: text/html

[C++] 158 HTTPプロトコル リクエスト送信

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

引き続き『C言語によるTCP/IPネットワークプログラミング』のサンプルコード読解です。

サンプルコードは手持ちのコードをつないで動いたものを載せている感が強いので、こちらで勝手にリファクタリングしていきます。

2001年に最前線の技術について著していることに価値があるのであって、単にコードを洗練させるというのは誰にでもできます。

前回記事にしたSOCprintf関数は廃止して、DoHttpGet関数内に移しました。

サンプルコードのような例外処理はあまり考えずに書きました。

// HTTP取得
int DoHttpGet(const char *host, const char *path)
{
	int	soc;
	const char *ptr;
	char request_char[4096];

	fprintf(stderr,"host = %s,path = %s\n", host, path);

	// ホストのHTTPに接続 
	if((soc = ConnectHost(host,"http",80)) == -1){
		fprintf(stderr,"Cannot connect to %s http.\n",host);
		return -1;
	}

	// リクエスト送信(旧SOCprintf関数)
	string request = "GET ";
	request += string(path);
	request += " HTTP/1.0\r\n\r\n";
	
	cout << "request " << request << endl;
	request.copy(request_char, 4095);

	send(soc, request_char, strlen(request_char), 0);
	
	// データ受信 
	if((ptr = strrchr(path,'/'))!=NULL){
		if(strlen(ptr+1)==0){
			SOCrecvDataToFile(soc,"noname");
		}
		else{
			SOCrecvDataToFile(soc,ptr+1);
		}
	}
	else{
		SOCrecvDataToFile(soc,path);
	}

	// ソケットクローズ 
	SocketClose(soc);

	return(0);
}

[C++] 157 va_listの機能調査

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

“[C++] 155 TCP/IP HTTPプロトコル”にあるSOCprintf関数の挙動を調べ、va_listの機能を把握しました。かなり特徴的な文法に思えます。

vsprintfとvfprintfは初見でした。

int SOCprintf(int num, ...)
{
	va_list args;
	int	soc;
	char *fmt;
	char buf[4096];

	// numは...内の個数
	va_start(args, num);

	// int型の引数を取得
	soc = va_arg(args, int);
	// char*型の引数を取得
	fmt = va_arg(args, char *);

	cout << "soc " << soc << endl;
	cout << "fmt " << fmt << endl;

	// 引数をformat変換しbufに格納
	vsprintf(buf, fmt, args);
	cout << "buf " << buf << endl;
	cout << "vsprintf pass" << endl;

	// format変換した引数を標準出力
	vfprintf(stdout, fmt, args);
	// vfprintf(stderr,fmt,args); // 旧コード
	cout << "vfprintf pass" << endl;

	// 引数のリストをクリア
	va_end(args);

	// リクエスト送信(socはソケット記述子の整数)
	send(soc, buf, strlen(buf), 0);

	return 0;
}

[C++] 156 strtok関数の書き換え

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, NO IDE]

HTTPプロトコルのサンプルコードで初めてstrtok関数を目にしました。文字列を区切り文字で分割したプレフィックスのポインタを返す関数です。

この関数を使った箇所を私なりに類推しながら理解しようとしましたがさっぱり分からず、ネットで意味を調べました。この関数を使うメリットは多々あるようですが、正直使いづらいです。C++でsplitする関数が一向に創出されないのは、使いづらいけど性能は相当高いstrtok関数のせいではないかと思いました。

そのような事情はさておき、私が使いやすいように自製クラスを用いて大幅に書き換えました。

個人的にはif文の条件内で処理をしTrue/Falseで条件分岐するコードはあまり好きではありません。まさか、このような書き方ができればよりハイスキルとか言ってなければいいのですが。コードサイズを切り詰める時代でもないですし、とにかく読みにくいです。

strtok関数については今のC/C++ユーザーはどのように対処しているのか気になります。

for(i = 1; i < argc; i++){
		// ホスト:パス 
		strcpy(buf, argv[i]);
		if((ptr = strtok(buf, ":")) == NULL){
			strcpy(host, "localhost");
			strcpy(path, argv[1]);

		} else {
			strcpy(host, ptr);
			if((ptr = strtok(NULL, "")) == NULL){
				fprintf(stderr, "host:path error\n");
				return -1;
			}
			strcpy(path,ptr);
		}

		// HTTP取得実行 
		DoHttpGet(host,path);
}
#include "Split.h"

Split spt;

for(i = 1; i < argc; i++){
		// ホスト:パス 
		vector<string> arg_list = spt.splits(argv[i], ":");
		const char* arg0 = (arg_list[0]).c_str();
		const char* arg1 = (arg_list[1]).c_str();

		// HTTP取得実行
		DoHttpGet(arg0, arg1);
}