C++でファイルのアップロードを受け取り、ファイルの中身をHTMLで表示するプログラムです。
参考にしたページ
http://www-cms.phys.s.u-tokyo.ac.jp/~naoki/CIPINTRO/CCGI/multdisp.html
・HTML
- <!DOCTYPE html>
- <html>
- <head>複数ファイルのアップロード
- <meta charset="utf-8">
- </head>
- <body>
- <form action="upload.fcgi" method="POST" enctype="multipart/form-data">
- <input type="file" name="files" multiple>
- <input type="submit" value="submit">
- </form>
- </body>
- </html>
・C++ソースコードの説明
・12~85行目:Requestがアップロードかを判定
環境変数[CONTENT_TYPE]の文字列から
- [multipart/form-data]が含まれているか
- [boundary]の文字列を取得できるか
例としてGoogle ChromeでアップロードをするとCONTENT_TYPEは、以下のようになります。
(WebKitFormBoundary7uUEXkjnVYxPBKMIは、ユニークな値がはいります。)
multipart/form-data; boundary=----WebKitFormBoundary7uUEXkjnVYxPBKMI, referer: 呼び出し元のURL
boundaryの文字列は、アップロードされた各ファイルの本体と、フッタの区切りに使うので覚えておきます。
・90~123行目:ファイルの中身まで標準入力を進める
アップロードされたファイルは、標準入力stdinから以下の順で取得できます。
(複数ファイルがアップロードされた時は、この順に繰り返されます。)
- ヘッダ
- ファイル本体
- フッタ
例えば2つのファイルをアップロードすると、以下のように情報が送られてきます。
------WebKitFormBoundary7uUEXkjnVYxPBKMI[\r][\n] Content-Disposition: form-data; name="files"; filename="ファイル1"[\r][\n] Content-Type: application/octet-stream[\r][\n] [\r][\n] ファイル1の中身 [\r][\n] ------WebKitFormBoundary7uUEXkjnVYxPBKMI[\r][\n] Content-Disposition: form-data; name="files"; filename="ファイル2"[\r][\n] Content-Type: application/octet-stream[\r][\n] [\r][\n] ファイル2の中身 [\r][\n]
ヘッダの構成は以下のようになっています。
- [--]+[CONTENT_TYPEのboundary]で指定されるユニークな文字列[\r][\n]
- Content-Disposition[\r][\n]
- Content-Type[\r][\n][\r][\n]
[\r][\n][\r][\n]の後からがファイル本体になるので、そこまでスキップしています。
送信されたフォームのフィールド名やファイル名などを取得する際には、2の[Content-Disposition]から取得する必要がありますが、本記事では行っていません。
・136~174行目
ファイル本体とヘッダをstdinから取得し、HTMLとし標準出力に表示しています。
また、stdinから文字を取得する際に[boundary]の文字列と比較し、ヘッダは表示せずに終了します。
・C++
- #include <string>
- #include <stdio.h>
- #include <iostream>
- #include <stdlib.h> // getenv
- const int BOUNDARY_LENGTH = 8; // boundaryの文字数
- using namespace std;
- // ========================================================
- // Requestがアップロードか調べ、boundaryの文字列を返します。
- // ========================================================
- bool isUploadRequest( std::string & boundaryStr )
- {
- /*
- 環境変数[REQUEST_METHOD]に送信されたメソッド[GET/POST]が入ります。
- アップロードは、[POST]で送信されてきます。
- */
- std::string request_method = getenv( "REQUEST_METHOD" );
- if ( request_method != "POST" )
- {
- fprintf( stderr, "REQUEST_METHOD isn't POST.\n" );
- return false;
- }
- /*
- Uploadでは、環境変数[CONTENT_TYPE]に[multipart/form-data]が含まれています。
- */
- std::string content_type = getenv( "CONTENT_TYPE" );
- if ( content_type.empty() ||
- content_type.find( "multipart/form-data" ) == std::string::npos )
- {
- fprintf( stderr, "CONTENT_TYPE is error.\n" );
- return false;
- }
- fprintf( stderr, "%s\n", content_type.c_str());
- /*
- boundaryを取得します。
- */
- std::string::size_type index = content_type.find( "boundary" );
- if ( index == std::string::npos )
- {
- return false;
- }
- // boundaryの文字列より後に[=]があるかを調べます。
- index = content_type.find( "=", index + BOUNDARY_LENGTH );
- if ( index == std::string::npos )
- {
- return false;
- }
- // [=]の後からboundaryを取得します。
- index++;
- if ( index >= content_type.size())
- {
- return false;
- }
- std::string::size_type end_index = std::string::npos;
- if ( content_type[ index ] == '"' )
- {
- // [boundary="XXXXXX"]形式は、["]内の文字列を取得します。
- end_index = content_type.find( '"', index + 1 );
- if ( end_index == std::string::npos )
- {
- return false;
- }
- index++;
- }
- else
- {
- // [boundary=XXXXX, XXXX=XXXX]形式は、[,]までの文字列を取得します。
- end_index = content_type.find( ',', index + 1 );
- }
- /*
- ファイル本体とフッタの区切り文字列を作成します。
- 区切りの文字列は、[\r\n--]+[boundaryの文字列]の形式です。
- */
- boundaryStr = "\r\n--";
- boundaryStr += content_type.substr( index, end_index - index );
- return true;
- }
- // ========================================================
- // ファイルの中身の直前には、[\r\n\r\n]があるので、そこまで進めます。
- // ========================================================
- bool skipHeader()
- {
- char buf;
- while ( 1 )
- {
- if ( fread( &buf, 1, 1, stdin ) != 1 )
- return false;
- if ( buf != '\r' )
- continue;
- if ( fread( &buf, 1, 1, stdin ) != 1 )
- return false;
- if ( buf != '\n' )
- continue;
- if ( fread( &buf, 1, 1, stdin ) != 1 )
- return false;
- if ( buf != '\r' )
- continue;
- if ( fread( &buf, 1, 1, stdin ) != 1 )
- return false;
- if ( buf == '\n' )
- {
- // [\r\n\r\n]が見つかったので、ループを抜けます。
- return true;
- }
- }
- return false;
- }
- // ========================================================
- // アップロードされたファイルの中身を表示します。
- // ========================================================
- bool printFile( const std::string & boundaryStr )
- {
- // ヘッダ部分をスキップし、ファイルの中身まで進めます。
- if ( !skipHeader())
- {
- return false;
- }
- // ファイルの中身を表示します。
- char buf;
- string tmpStr;
- string::size_type boundaryLength = boundaryStr.size();
- string::size_type index = 0;
- while ( 1 )
- {
- if ( fread( &buf, 1, 1, stdin ) != 1 )
- return true;
- if ( buf == boundaryStr[ index ] )
- {
- /*
- boundaryの文字なので、一旦tmpStrに保存し、比較するboundaryのインデックスを進めます。
- */
- index++;
- tmpStr += buf;
- if ( index >= boundaryLength )
- {
- // boundaryの文字が全て見つかったので、ループを抜けます。
- break;
- }
- }
- else
- {
- if ( index > 0 )
- {
- // boundaryでない文字列が見つかったので、tmpStrの文字列を表示しインデックスを0に戻します。
- printf( "%s", tmpStr.c_str());
- index = 0;
- tmpStr.clear();
- }
- putchar( buf );
- }
- }
- return true;
- }
- // ========================================================
- // main
- // ========================================================
- int main()
- {
- // Requestsがアップロードかを調べます。
- std::string boundaryStr;
- if ( !isUploadRequest( boundaryStr ))
- {
- return -1;
- }
- // HTMLを出力します。
- printf( "Content-Type: text/html\n\n" );
- printf( "<html>" );
- printf( "<head><title>test</title></head>" );
- printf( "<body>" );
- std::string boundary;
- if ( !isUploadRequest( boundary ))
- {
- printf( "error\n" );
- }
- else
- {
- while ( 1 )
- {
- if ( !printFile( boundaryStr )) break;
- }
- }
- printf( "</body>" );
- printf( "</html>" );
- return 0;
- }
0 コメント:
コメントを投稿