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;
}

Posted in: 
0 コメント:
コメントを投稿