harとはhttp archiveであり、ブラウザのやり取りのログのことです。
このharファイルはjson(JavaScript Object Notation)という形式で書かれています。
名前にJavaScriptとあるためjsで扱えたり、他にもpythonとかまあなんでも取り扱う方法があるようです。
簡単にできるんだったらjsでやろうかなと思って調べるとNodejsをインストールするとかありました。
私はNodejsとかもうさっぱりちんぷんかんぷんなので使えません。
まあただのテキストデータなのでいつも使っているc言語で扱ってみます。

harの構造

harのファイルフォーマットはググってみるとドキュメントが放棄されたとか書かれていましたがどういうことなんでしょうか。
よくわからないですが、
ここらへんに構造が書いてあります。
しかしharファイルの中身全てをパースするのは面倒だし、htmlやcssを見ても面白くないので今回は画像ファイルを中心に扱ってみます。

てきとーなサイトでDevtoolsからharを保存してみてみるといろんなオブジェクトタイプがあることがわかります。
その中でもわかりやすいのが"request"と"response"です。

"request": {
    "method": "GET",
    "url": "https://~~~~",
    ~~~
},
"response": {
    "status": 200,
    "statusText": "",
    "httpVersion": "http/2.0",
    "headers": [
        ~~~
    ],
    "cookies": [],
    "content": {
        "size": 52,
        "mimeType": "image/jpeg",
        "text": "dGhpcyBpcyBpbWFnZSBkYXRhIGVuY29kZWQgYnkgYmFzZTY0Lg=="
        "encoding": "base64"
    },
    ~~~
},

こんな感じにありました。
サーバーに"GET"リクエストを送って、レスポンスとして"content"の中にデータがあるようです。
"mimeType"が"image/jpeg"のため、.jpgファイルだということがわかります。
"text"が画像データっぽいですね。
上のはサンプルなので画像データではないですが。
"encoding"をみるとbase64でエンコードされていることがわかります。
ということは、"mimeType"が"image/jpeg"の"text"を取り出してbase64でデコードすればいいことがわかりました。

パースする

パースというか画像を取り出すには"mimeType": "image/jpeg"を探して、見つかったら"text": を探し、そのあとに続く""の中身をbase64でデコードしてファイルに保存するという流れを思いつきました。
mimeTypeは"image/png"のときもあるので注意です。
保存するファイル名もURLから取り出そうと思ったのですが、面倒だったので連番にしました。
あとは文字列探索ですが、なんかいろいろあるっぽいのですがKMP法を採用しました。
でもそれは後から知ったことで、自分で思いつきました(どや顔)


#include<stdio.h>
#include<string.h>
#include<windows.h>

int b64table(int b64)
{
	int ascii=-1;
	//0~9 48~57 -> 52~61
	if (b64 >= 48 && b64 <= 57)ascii = b64 + 4;
	//A~Z 65~90 -> 0~25
	if (b64 >= 65 && b64 <= 90)ascii = b64 - 65;
	//a~z 97~122 -> 26~51
	if (b64 >= 97 && b64 <= 122)ascii = b64 - 71;
	//'+' 43 -> 62
	if (b64 == 43)ascii = b64 + 19;
	//'/' 47 -> 63
	if (b64 == 47)ascii = b64 + 16;
	//'=' 61 -> 0
	if (b64 == 61)ascii = 0;
	//error
	if (b64 == -1)printf("ascii=%d\n",b64);
	return ascii;
}

void b64decode(unsigned char *buf)
{
	unsigned char de[3];
	unsigned char a=buf[0];
	unsigned char b=buf[1];
	unsigned char c=buf[2];
	unsigned char d=buf[3];
	de[0]=(b64table(a)<<2)+(b64table(b)>>4);
	de[1]=((b64table(b)&0b1111)<<4)+(b64table(c)>>2);
	de[2]=((b64table(c)&0b11)<<6)+b64table(d);
	for(int i=0;i<3;i++)buf[i]=de[i];
}

int searchstr(char buf,char *str)
{
	int flag=0;
	static int n=0;
	if(buf==str[n])n++;
	else if(buf==str[0])n=1;
	else n=0;
	if(n==strlen(str))flag=1;
	return flag;
}

int main()
{
	char filepath[200],cho[10];
	puts("D&D har file. Extract image file.");
	puts("choose jpg or png.");
	puts("example: C:\\a.har png");
	scanf("%s %s",filepath,cho);
	FILE *fp=fopen(filepath,"rb");
	int endF=0;
	int n=0;
	CreateDirectory("extracted",NULL);
	while(1){
		while(1){
			char c=fgetc(fp);
			if(c==-1){
				endF=1;
				break;
			}
			if(strcmp("png",cho)==0){if(searchstr(c,"\"image/png\"")==1)break;}
			else if(searchstr(c,"\"image/jpeg\"")==1)break;
		}
		if(endF)break;
		puts("found image key");
		while(1){
			char c=fgetc(fp);
			if(searchstr(c,"text\": \"")==1)break;
		}
		puts("found text key");
		char fileName[20];
		if(strcmp("png",cho)==0)sprintf(fileName,"extracted\\%d.png",n);
		else sprintf(fileName,"extracted\\%d.jpg",n);
		FILE *fp2=fopen(fileName,"wb");
		while(1){
			unsigned char c[4];
			fread(c,1,4,fp);
			if(c[0]=='\"')break;
			b64decode(c);
			fwrite(c,1,3,fp2);
		}
		puts("saved text value");
		n++;
	}
	fclose(fp);
	return 0;
}


久しぶりにchromeのdev-toolsでNetworkを見てみると、画像のmimeTypeがoctet-streamになっていることがありました。
なんだこの形式は!?
ストリームってことはWSタブにあるのかと思って見てみてもありませんでした。
いったいどこにと思ったら普通にapplication/octet-streamというmimeTypeのところにありました。
どんなmimetypeでもパースできる万能なものを作ろうと思ったら拡張子をmimeTypeに対応させないといけないためにめんどくさくなって諦めました。
判定の部分の文字列を書き換えて動かせばいいや。
いや、判定するmimeTypeと保存する拡張子を引数として渡せばいいじゃん!
でも判定の部分を書き換えて済んじゃったのでまたやる気が出たらやろう…

そしてやる気が出た。
なんかパラメータがめっちゃ増えた。
あとc言語の文字列の処理を自力で実装したら、そういえばsprintfでできんじゃんと気づいた…

#include<stdio.h>
#include<string.h>
#include<windows.h>

int b64table(int b64)
{
int ascii=-1;
//0~9 48~57 -> 52~61
if (b64 >= 48 && b64 <= 57)ascii = b64 + 4;
//A~Z 65~90 -> 0~25
if (b64 >= 65 && b64 <= 90)ascii = b64 - 65;
//a~z 97~122 -> 26~51
if (b64 >= 97 && b64 <= 122)ascii = b64 - 71;
//'+' 43 -> 62
if (b64 == 43)ascii = b64 + 19;
//'/' 47 -> 63
if (b64 == 47)ascii = b64 + 16;
//'=' 61 -> 0
if (b64 == 61)ascii = 0;
//error
if (b64 == -1)printf("ascii=%d\n",b64);
return ascii;
}

void b64decode(unsigned char *buf)
{
unsigned char de[3];
unsigned char a=buf[0];
unsigned char b=buf[1];
unsigned char c=buf[2];
unsigned char d=buf[3];
de[0]=(b64table(a)<<2)+(b64table(b)>>4);
de[1]=((b64table(b)&0b1111)<<4)+(b64table(c)>>2);
de[2]=((b64table(c)&0b11)<<6)+b64table(d);
for(int i=0;i<3;i++)buf[i]=de[i];
}

int searchstr(char buf,char *str)
{
int flag=0;
static int n=0;
if(buf==str[n])n++;
else if(buf==str[0])n=1;
else n=0;
if(n==strlen(str))flag=1;
return flag;
}

int main(){
char filepath[200],cho[50], ex[20], fol[30];
puts("Extract data from har file.");
puts("D&D har file and enter mimeType,  extension, folder name.");
puts("example: C:\\a.har image/png png folder1");
puts("other mimeType: application/octet-stream, image/jpeg, etc.");
scanf("%s %s %s %s",filepath,cho,ex,fol);
FILE *fp=fopen(filepath,"rb");
int endF=0;
int n=0;
char mT[50];
sprintf(mT,"\"%s\"",cho);
/*
mT[0]='\"';
for(int i=0;i<strlen(cho);i++){
mT[i+1]=cho[i];
}
mT[strlen(cho)+1]='\"';
mT[strlen(cho)+2]='\0';
*/
CreateDirectory(fol,NULL);
while(1){
while(1){
char c=fgetc(fp);
if(c==-1){
endF=1;
break;
}
if(searchstr(c,mT)==1)break;
}
if(endF)break;
puts("found mimeType");
while(1){
char c=fgetc(fp);
if(searchstr(c,"text\": \"")==1)break;
}
puts("found text");
char fileName[20];
sprintf(fileName,"%s\\%d.%s",fol,n,ex);
FILE *fp2=fopen(fileName,"wb");
while(1){
unsigned char c[4];
fread(c,1,4,fp);
if(c[0]=='\"')break;
b64decode(c);
fwrite(c,1,3,fp2);
}
printf("%d: saved text value",n+1);
n++;
}
fclose(fp);
return 0;
}