CGI

PerlCGIを書く場合は、CGIモジュールを使うと便利。

CGI - Common Gateway Interface のリクエストとレスポンスを扱う - perldoc.jp

まずは、newでCGIオブジェクトを生成し、CGIオブジェクトが何なのか見てみる。

CGIと言いつつ、perlコマンドで実行すると、

$ vi CGI.pl
$ perl CGI.pl
CGI=HASH(0x13c9810)
$

なるほど。
中身はハッシュなのかな。

Sample/perl/CGI/CGI/src/CGI at master · bg1bgst333/Sample · GitHub

AuthConfig

認証設定を.htaccessに書きたい場合は、httpd.confに"AllowOverride AuthConfig"と書く。

認証、承認、アクセス制御 - Apache HTTP サーバ バージョン 2.4

$ sudo vi /etc/httpd/conf/httpd.conf
[sudo] password for bg1:

httpd.confを開いて、

# access_test2
<Directory /var/www/html/access_test/access_test2/>
    AllowOverride AuthConfig
</Directory>

前回の設定は消して、こう書く。

AllowOverride AuthConfig
AllowOverride AuthConfig

AllowOverride AuthConfig。
あとは、

$ cd /var/www/html/access_test/access_test2/
$ vi .htaccess
$ cat .htaccess
AuthType Basic
AuthName "Authentication"
AuthUserFile /etc/httpd/htpasswd
Require valid-user
$

.htaccessに前回と同様認証設定を書く。

$ sudo service httpd restart
Redirecting to /bin/systemctl restart  httpd.service
$

httpd.confを書き換えてるので再起動。

前回同様、認証ダイアログが出た。
前回同様、認証ダイアログが出た。

前回同様、認証ダイアログが出た。

正しいパスワードで
正しいパスワードで

正しいパスワードで、

アクセスできる
アクセスできる

アクセスできる。

AuthType

AuthTypeディレクティブは、どの認証方法を使うかを指定する。
今回は、Apacheで簡単なBasic認証を仕掛ける。

認証、承認、アクセス制御 - Apache HTTP サーバ バージョン 2.4
ApacheでBASIC認証(パスワード認証)を設定する | Points & Lines

まず、認証をかけるディレクトリの作成。

$ cd /var/www/html/access_test/
$ ls
access_test1
$ mkdir access_test2
$ cd access_test2/
$ vi access2.html
$ cat access2.html
<html>
  <head>
    <title>AuthType</title>
  </head>
  <body>
    <a href="http://bgstation0.com/">B.G-STATION</a>
  </body>
</html>
$

access_test2ディレクトリを作成し、access2.htmlを置く。
次に、認証をかけたディレクトリにアクセス可能なユーザとパスワードの作成。

$ sudo htpasswd -c /etc/httpd/htpasswd testuser1
[sudo] password for bg1:
New password:
Re-type new password:
Adding password for user testuser1
$

htpasswdコマンドで、パスワードファイル/etc/httpd/htpasswdを作成し、testuser1を追加。

$ cat /etc/httpd/htpasswd
testuser1:$apr1$WsD6GAR6$FjhQF5tq58tk7RJDLcuXA.
$

保存したパスワード自体は暗号化されている。

$ sudo vi /etc/httpd/conf/httpd.conf

httpd.confを開いて、

# access_test2
<Directory /var/www/html/access_test/access_test2/>
    AuthType Basic
    AuthName "Authentication"
    AuthUserFile /etc/httpd/htpasswd
    Require valid-user
</Directory>

このようなセクションを追加。

今回は.htaccessじゃなくて直接書いた
今回は.htaccessじゃなくて直接書いた

今回は.htaccessじゃなくて直接書いた。
AuthTypeは認証方法。Basic認証なので、"AuthType Basic"。
AuthNameは、ブラウザによっては表示されるダイアログのメッセージ。
AuthUserFileがパスワードファイル。
Requireは対象ユーザ(認証通れば見れるユーザ)なんだけど、valid-userにすればパスワードファイルに書いてある人はだれでも見れる。

Apache再起動前は誰でもアクセスできる
Apache再起動前は誰でもアクセスできる

Apache再起動前は誰でもアクセスできる。

Apache再起動後はユーザとパスワード要求
Apache再起動後はユーザとパスワード要求

Apache再起動後はユーザとパスワード要求。

間違えると
間違えると

間違えると、

弾かれる
弾かれる

弾かれる。

正しければ通る
正しければ通る

正しければ通る。

Limit

AllowOverrideでLimitを指定した場合は、IPアクセス制限を上書きできるということ。

core - Apache HTTP サーバ バージョン 2.4
もっとhttpd.confの設定

(元来、<Limit>の形式で使われる<Limit>ディレクティブからきてるとおもうけど、ちょっと書き方違うね。)

$ cd /var/www/html/access_test/access_test1/
$ cat .htaccess
Order allow,deny
Allow from none
Deny from all

$

この状態だと、

当然Forbiddenになっている
当然Forbiddenになっている

当然Forbiddenになっている。

$ curl -I http://bgstation0.com/access_test/access_test1/access1.html
HTTP/1.1 403 Forbidden
Date: Wed, xx --- 2021 xx:xx:xx GMT
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.1e-fips PHP/5.4.16
Content-Type: text/html; charset=iso-8859-1

$

ローカルからcurlでもForbidden。

自身のIPを調べて
自身のIPを調べて

自身のIPを調べて、

$ vi .htaccess
$ cat .htaccess
Order deny,allow
Deny from all
Allow from xx.xxx.xx.xxx

$

今度は自身からのリクエストのみ受け付ける。

こっちは変わらない
こっちは変わらない

こっちは変わらない。

$ curl http://bgstation0.com/access_test/access_test1/access1.html
<html>
  <head>
    <title>.htaccess</title>
  </head>
  <body>
    <a href="http://bgstation0.com/">B.G-STATION</a>
  </body>
</html>
$ curl -I http://bgstation0.com/access_test/access_test1/access1.html
HTTP/1.1 200 OK
Date: ---, xx --- 2021 xx:xx:xx GMT
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.1e-fips PHP/5.4.16
Last-Modified: ---, xx --- 2021 xx:xx:xx GMT
ETag: "xx-xxxxxxxxxxxxx"
Accept-Ranges: bytes
Content-Length: 135
Content-Type: text/html; charset=UTF-8

$

自身からのアクセスのみ受け付ける。
ちなみにIPを調べたのは127.0.0.1localhostだと上手くいかないから。

AllowOverride

".htaccess"ファイルを有効にするには、AllowOverrideディレクティブを書く必要がある。

core - Apache HTTP サーバ バージョン 2.4

httpd.confに、

# access_test1
<Directory /var/www/html/access_test/access_test1/>
    AllowOverride Limit
</Directory>

Directoryディレクティブで設定変更可能にするパスを指定。
ホストへのアクセス制御部分のみの上書きするなら、"AllowOverride"の後に"Limit"を指定。

実際の画像
実際の画像

実際の画像。
これでApache再起動すると、

Forbiddenになった
Forbiddenになった

Forbiddenになった。
これでどこからもアクセス拒否になってる。

access1.htmlという1ファイルだけ拒否ではなくてaccess_test1ディレクトリごと拒否してることがこれでわかる。
access1.htmlという1ファイルだけ拒否ではなくてaccess_test1ディレクトリごと拒否してることがこれでわかる。

access1.htmlという1ファイルだけ拒否ではなくてaccess_test1ディレクトリごと拒否してることがこれでわかる。

.htaccess

Apacheで、ディレクトリ毎に設定を変えたい場合、httpd.confを修正する方法とは別に、ディレクトリごとに".htaccess"ファイルを置いて、そこに設定を書くという方法もある。

認証、承認、アクセス制御 - Apache HTTP サーバ バージョン 2.4
Apache チュートリアル: .htaccess ファイル - Apache HTTP サーバ バージョン 2.4

ということで、

$ cd /var/www/html/
$ mkdir access_test
$ cd access_test/
$ pwd
/var/www/html/access_test
$ mkdir access_test1
$ ls
access_test1
$ cd access_test1/
$ pwd
/var/www/html/access_test/access_test1
$

まず、access_test1という検証ディレクトリを作成。

$ vi access1.html
$ cat access1.html
<html>
  <head>
    <title>.htaccess</title>
  </head>
  <body>
    <a href="http://bgstation0.com/">B.G-STATION</a>
  </body>
</html>
$

そこに、access1.hmtlという簡単なページを作っておく。

この時点ではまだアクセス可
この時点ではまだアクセス可

この時点ではまだアクセス可。

$ vi .htaccess
$ cat .htaccess
Order allow,deny
Allow from none
Deny from all

$

ここに.htaccessを作成し、"Deny from all"で、すべてのIPからの接続を拒否するように設定。

$ sudo service httpd restart
Redirecting to /bin/systemctl restart  httpd.service
$

Apacheを再起動。

あれ?アクセスできちゃう
あれ?アクセスできちゃう

あれ?アクセスできちゃう。
そう、これだけじゃ.htaccessは反映されない。

setmode

setmodeは、標準入出力をバイナリモードに切り替えたり、テキストモードに戻したりする時に使う。
バイナリモード/テキストモードの切り替えに関する問題は、Windowsでしか起きないので、Windowsのみ用意されている。
(BSDにも同名関数があるが、機能が違う。)

setmode | Microsoft Docs

で、これをよく使うのがWindowsCGIなのだが、そのためにWindowsにもApacheを入れる。

Apache インストール (Windows編) - プログラミングスタイル

ダウンロードしたら、解凍して、

配置
配置

配置。

パスを通す
パスを通す

パスを通す。

バージョンはこう出る
バージョンはこう出る

バージョンはこう出る。

httpd.confを見ると、

Define SRVROOT "c:/Apache24"

ServerRoot "${SRVROOT}"

ServerRootはここ。

#ServerName www.example.com:80
ServerName localhost:80

ServerNameはひとまずlocalhostに修正。

DocumentRoot "${SRVROOT}/htdocs"
<Directory "${SRVROOT}/htdocs">

DocumentRootはこうなっている。

httpd.exeをクリックするだけで起動する
httpd.exeをクリックするだけで起動する

httpd.exeをクリックするだけで起動する。

管理者権限でコマンドプロンプトを起動し、

サービス登録
サービス登録

サービス登録。

こうしておけば、こっから起動できる
こうしておけば、こっから起動できる

こうしておけば、こっから起動できる。

いつもの
いつもの

いつもの。
CGIcgi-binフォルダに入れるだけで実行できるみたい。
VisualStudioでCGIを書く前に、

いいえにしておく
いいえにしておく

古い書き方だと引っ掛かるのでこれをいいえにしておく。

action.cで、

/* ヘッダファイルのインクルード */
#include <stdio.h>  /* 標準入出力 */
#include <stdlib.h> /* 標準ユーティリティ */
#include <string.h> /* 文字列処理 */

/* main関数の定義 */
int main(void) {

    /* 変数の宣言・初期化 */
    char* buf = NULL;	/* char型バッファポインタbufをNULLで初期化. */
    char* content_type = NULL;	/* char型ポインタcontent_typeをNULLで初期化. */
    char* p1 = NULL;	/* char型ポインタp1をNULLで初期化. */
    char* p2 = NULL;	/* char型ポインタp2をNULLで初期化. */
    char boundary[256] = { 0 };	/* char型バッファboundary(長さ256)を{0}で初期化. */
    char* content_length = NULL;	/* char型ポインタcontent_lengthをNULLで初期化. */
    int len = 0;	/* content_lengthを整数値に変換したものlenを0で初期化. */
    char pattern[256] = { 0 };	/* 検索するboundary文字列pattern. */
    int read_len = 0;	/* 読み込めた長さread_len. */
    char* p3 = NULL;	/* char型ポインタp3をNULLで初期化. */
    char* p4 = NULL;	/* char型ポインタp4をNULLで初期化. */
    char* p5 = NULL;	/* char型ポインタp5をNULLで初期化. */
    int ret = 0;	/* memcmpの戻り値retを0で初期化. */
    int i = 0;	/* ループ用変数iを0で初期化. */
    int rest = 0;	/* コンテンツ先頭からファイル先頭までの無駄な部分のサイズrestを0で初期化. */
    char file_content_type[256] = { 0 };	/* ファイルコンテントタイプ(長さ256)を{0}で初期化. */
    char* file_buf = NULL;	/* char型バッファポインタfile_bufをNULLで初期化. */
    
    /* CGI処理. */
    content_type = getenv("CONTENT_TYPE");  /* getenvでcontent_typeを取得. */
    p1 = strstr(content_type, "multipart/form-data");   /* "multipart/form-data"を探す. */
    if (p1 != NULL) {   /* 見つかった場合. */
        p2 = strstr(p1, "boundary=");   /* "boundary="を探す. */
        if (p2 != NULL) {   /* 見つかった場合. */
            strcpy(boundary, p2 + strlen("boundary=")); /* boundaryを取り出す. */
            content_length = getenv("CONTENT_LENGTH");  /* getenvでcontent_lengthを取得. */
            len = atoi(content_length); /* 整数値に変換. */
            buf = (char*)malloc(sizeof(char) * (len + 1));  /* mallocでbufを確保. */
            memset(buf, 0, len + 1);    /* memsetでbufをクリア */
            read_len = fread(buf, sizeof(char), len, stdin);    /* freadで読み込み. */
            if (len == read_len) {  /* 一致 */
                strcat(pattern, "--");  /* まず"--"をセット. */
                strcat(pattern, boundary);  /* boundaryを連結. */
                p3 = strstr(buf, pattern);  /* bufからpatternを見つける. */
                if (p3 != NULL) {   /* 見つかった場合. */
                    p4 = strstr(p3, "Content-Type: ");  /* "Content-Type"を探す. */
                    if (p4 != NULL) {   /* 見つかった場合. */
                        p5 = strstr(p4, "\r\n\r\n");    /* "\r\n\r\n"を探す. */
                        if (p5 != NULL) {   /* 見つかった場合. */
                            strncpy(file_content_type, p4 + strlen("Content-Type: "), p5 - (p4 + strlen("Content-Type: ")));    /* file_content_typeの抽出. */
                            rest = len - ((p5 + 4) - buf);  /* restを計算. */
                            for (i = 0; i < rest; i++) {
                                ret = memcmp(p5 + 4 + i, pattern, strlen(pattern)); /* 比較. */
                                if (ret == 0) { /* 一致 */
                                    file_buf = (char*)malloc(sizeof(char) * (i - 1));   /* mallocでfile_bufを確保. */
                                    memset(file_buf, 0, i - 1); /* memsetでfile_bufをクリア */
                                    memcpy(file_buf, p5 + 4, i - 2);    /* file_bufにコピー. */
                                    printf("Content-type: %s\n", file_content_type);    /* "Content-type: "でfile_content_typeを出力. */
                                    printf("Content-length: %d\n", i - 2);  /* "Content-length: "でi - 2を出力. */
                                    printf("\n");   /* printfで改行を出力. */
                                    fwrite(file_buf, sizeof(char), i - 2, stdout);  /* 書き込み */
                                    free(file_buf); /* 解放 */
                                    break;  /* 抜ける. */
                                }
                            }
                        }
                    }
                }
            }
            free(buf);  /* bufを解放. */
        }
    }

    /* プログラムの終了 */
    return 0;   /* 0を返して正常終了. */

}

Linuxと同じ書き方だと、

submit押すと
submit押すと

submit押すと、

エラーになっちゃう
エラーになっちゃう

エラーになっちゃう。
Windowsはテキストモードだと読み込むデータに"\r\n"が入ってると"\n"に変換しちゃう。
なので、

/* ヘッダファイルのインクルード */
#include <stdio.h>  /* 標準入出力 */
#include <stdlib.h> /* 標準ユーティリティ */
#include <string.h> /* 文字列処理 */
#include <fcntl.h>  /* ファイル制御 */
#include <io.h>     /* 入出力 */

/* main関数の定義 */
int main(void) {

    /* 変数の宣言・初期化 */
    char* buf = NULL;	/* char型バッファポインタbufをNULLで初期化. */
    char* content_type = NULL;	/* char型ポインタcontent_typeをNULLで初期化. */
    char* p1 = NULL;	/* char型ポインタp1をNULLで初期化. */
    char* p2 = NULL;	/* char型ポインタp2をNULLで初期化. */
    char boundary[256] = { 0 };	/* char型バッファboundary(長さ256)を{0}で初期化. */
    char* content_length = NULL;	/* char型ポインタcontent_lengthをNULLで初期化. */
    int len = 0;	/* content_lengthを整数値に変換したものlenを0で初期化. */
    char pattern[256] = { 0 };	/* 検索するboundary文字列pattern. */
    int read_len = 0;	/* 読み込めた長さread_len. */
    char* p3 = NULL;	/* char型ポインタp3をNULLで初期化. */
    char* p4 = NULL;	/* char型ポインタp4をNULLで初期化. */
    char* p5 = NULL;	/* char型ポインタp5をNULLで初期化. */
    int ret = 0;	/* memcmpの戻り値retを0で初期化. */
    int i = 0;	/* ループ用変数iを0で初期化. */
    int rest = 0;	/* コンテンツ先頭からファイル先頭までの無駄な部分のサイズrestを0で初期化. */
    char file_content_type[256] = { 0 };	/* ファイルコンテントタイプ(長さ256)を{0}で初期化. */
    char* file_buf = NULL;	/* char型バッファポインタfile_bufをNULLで初期化. */
    
    /* CGI処理. */
    content_type = getenv("CONTENT_TYPE");  /* getenvでcontent_typeを取得. */
    p1 = strstr(content_type, "multipart/form-data");   /* "multipart/form-data"を探す. */
    if (p1 != NULL) {   /* 見つかった場合. */
        p2 = strstr(p1, "boundary=");   /* "boundary="を探す. */
        if (p2 != NULL) {   /* 見つかった場合. */
            strcpy(boundary, p2 + strlen("boundary=")); /* boundaryを取り出す. */
            content_length = getenv("CONTENT_LENGTH");  /* getenvでcontent_lengthを取得. */
            len = atoi(content_length); /* 整数値に変換. */
            buf = (char*)malloc(sizeof(char) * (len + 1));  /* mallocでbufを確保. */
            memset(buf, 0, len + 1);    /* memsetでbufをクリア */
            setmode(fileno(stdin), O_BINARY);   // setmodeでstdinをバイナリモードにセット. */
            read_len = fread(buf, sizeof(char), len, stdin);    /* freadで読み込み. */
            if (len == read_len) {  /* 一致 */
                strcat(pattern, "--");  /* まず"--"をセット. */
                strcat(pattern, boundary);  /* boundaryを連結. */
                p3 = strstr(buf, pattern);  /* bufからpatternを見つける. */
                if (p3 != NULL) {   /* 見つかった場合. */
                    p4 = strstr(p3, "Content-Type: ");  /* "Content-Type"を探す. */
                    if (p4 != NULL) {   /* 見つかった場合. */
                        p5 = strstr(p4, "\r\n\r\n");    /* "\r\n\r\n"を探す. */
                        if (p5 != NULL) {   /* 見つかった場合. */
                            strncpy(file_content_type, p4 + strlen("Content-Type: "), p5 - (p4 + strlen("Content-Type: ")));    /* file_content_typeの抽出. */
                            rest = len - ((p5 + 4) - buf);  /* restを計算. */
                            for (i = 0; i < rest; i++) {
                                ret = memcmp(p5 + 4 + i, pattern, strlen(pattern)); /* 比較. */
                                if (ret == 0) { /* 一致 */
                                    file_buf = (char*)malloc(sizeof(char) * (i - 1));   /* mallocでfile_bufを確保. */
                                    memset(file_buf, 0, i - 1); /* memsetでfile_bufをクリア */
                                    memcpy(file_buf, p5 + 4, i - 2);    /* file_bufにコピー. */
                                    printf("Content-type: %s\n", file_content_type);    /* "Content-type: "でfile_content_typeを出力. */
                                    printf("Content-length: %d\n", i - 2);  /* "Content-length: "でi - 2を出力. */
                                    printf("\n");   /* printfで改行を出力. */
                                    fwrite(file_buf, sizeof(char), i - 2, stdout);  /* 書き込み */
                                    free(file_buf); /* 解放 */
                                    break;  /* 抜ける. */
                                }
                            }
                        }
                    }
                }
            }
            free(buf);  /* bufを解放. */
        }
    }

    /* プログラムの終了 */
    return 0;   /* 0を返して正常終了. */

}

freadの前に、

setmode(fileno(stdin), O_BINARY);

を置いて、stdinをバイナリモードにする。

今度はsubmitすると
今度はsubmitすると

今度はsubmitすると、

改行が増えていて、最後の2文字を書き込めなかった。
改行が増えていて、最後の2文字を書き込めなかった。

改行が増えていて、最後の2文字を書き込めなかった。
これは、fwriteの時に、"\r\n"が"\r\r\n"に変換されているのが問題なので、

fwriteの前に、

を置いて、バイナリのまま出力するようにする。
これで、

submitすると
submitすると

submitすると、

こうなる
こうなる

こうなる。

画像も
画像も

画像も、

表示できる
表示できる

表示できる。

Sample/c/setmode/setmode/src/setmode at master · bg1bgst333/Sample · GitHub