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 コメント:
コメントを投稿