jFD2の関連付けと外部コマンドのバグを何とかしてみた

※2012年10月18日、更新。



jFD2をLinuxで動かす場合、ファイル名/フォルダ名にシェルの特殊文字が使われていると、関連付けられたアプリで開けなかったり外部コマンドに渡せなかったりする、とんでもなく致命的なバグがあるのだが、いつか直るだろうと思っていたらバージョンアップされないまま1年以上が過ぎてしまったので、仕方なくバグを回避するツールを作ってみた。

これを使ってもまだ、ファイル名に$や連続した半角スペースが含まれているとエラーが出る。

以下、ソース。Ubuntu以外で使いたい場合はコンパイルして下さい。

/*
 * jfd2-open.c
 * 
 * バージョン 2012年10月18日 細かい修正
 * バージョン 2012年10月15日 パイプのバッファの処理を修正
 * 
 * gcc -W -Wall -Wextra -std=c99 -o jfd2-open jfd2-open.c
*/

#define _XOPEN_SOURCE

#include "replace_string.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
/*
 * argv[0] == 実行ファイル名
 * argv[1] == コマンド名
 * argv[2] 以降 == 引数
*/
int main(int argc, char *argv[])
{
    if(argc == 1)
    {
        puts("jfd2-open コマンド名 引数");
        exit(EXIT_SUCCESS);
    }

    /* 引数を入れる配列。 ushortぐらいあれば充分だと思いたい。 */
    char argvcat[USHRT_MAX] = "";

    strcat(argvcat, argv[2]);

    /*
    * ファイル名に半角スペースがあると複数に分割されるので連結する。
    * 連続した半角スペースには対応不可。
    */
    {
        int i = 3;
        int j = argc - 1;

        while(i <= j)
        {
            strcat(argvcat, " ");
            strcat(argvcat, argv[i]);
            i++;
        }
    }

    int count_d_quart = 0;

    /*
     * jFD2はファイル名に半角スペースがあると、勝手に"で括る。
     * しかも"が一つとは限らない。
    */
    for(;;)
    {
        if(argvcat[count_d_quart] == '\"')
        {
            count_d_quart++;
        }
        else
        {
            break;
        }
    }

    if(count_d_quart > 0)
    {
        int i = count_d_quart;
        int j = strlen(argvcat);

        for(; i > 0; i--, j--)
        {
            argvcat[j] = '\0';
        }

        memmove(argvcat + 1, argvcat + count_d_quart, strlen(argvcat) + 1);
    }

    replace_string(argvcat, "'", "'\\''", USHRT_MAX);

    {
        int len = strlen(argvcat);

        /* 文字列の末尾に\'を代入する */
        if(argvcat[0] == '\"' && argvcat[1] == '/')
        {
            argvcat[len - 1] = '\'';
        }
        else
        {
            memmove(argvcat + 1, argvcat, len + 1);
            len++;
            argvcat[len] = '\'';
            argvcat[len + 1] = '\0';
        }
    }

    argvcat[0] = '\'';
    argvcat[USHRT_MAX - 1] = '\0';

    /* コマンド名 + 引数を入れる配列。 ushortぐらいあれば大丈夫だよね? */
    char command[USHRT_MAX] = "";

    strcat(command, argv[1]);
    strcat(command, " ");
    strcat(command, argvcat);

    FILE *popen_r = popen(command, "r");

    if(popen_r != NULL)
    {
        char buf[UCHAR_MAX];
        int i;

        /* パイプのバッファを空にするためにグルグル回す */
        while((i = fread(buf, 1, UCHAR_MAX, popen_r)) > 0)
        {
            for(int j = 0; j < i; j++)
            {
                putchar(buf[j]);
            }
        }

        pclose(popen_r);
    }

    return EXIT_SUCCESS;
}

/*
 * replace_string.h
 *
 * http://www.sasaraan.net/program/cpp/cpp_find.html
 * http://www.geocities.jp/debu0510/personal/c_moji.html
 * 辺りが参考になった
*/

#ifndef REPLACE_STRING_H
#define REPLACE_STRING_H

#include <stdlib.h>
#include <string.h>

/* 関数プロトタイプ */
static inline void replace_string(char *buf, char *before, char *after, int buf_len);

/*******************************************************************************
 * 文字列を置換する。
 * バッファサイズを考慮していない。置換後の文字列がバッファサイズを超える場合、
 * 多分落ちる。超えることは無いはずだが…
*******************************************************************************/
static inline void replace_string(char *buf, char *before, char *after, int buf_len)
{
    char *tmp = buf;

    for(;;)
    {
        char *point;
        size_t before_len;
        size_t after_len;

        before_len = strlen(before);
        after_len = strlen(after);

        if((point = strstr(tmp, before)) == NULL)
        {
            break;
        }

        tmp = point;
        memmove(point + after_len, point + before_len, buf_len - (point + before_len - buf) + 1);
        memcpy(point, after, after_len);
        tmp = tmp + after_len;
    }
}
#endif