2011/05/17

c++でcgi ー ファイルのアップロード

C++でファイルのアップロードを受け取り、ファイルの中身をHTMLで表示するプログラムです。

参考にしたページ
http://www-cms.phys.s.u-tokyo.ac.jp/~naoki/CIPINTRO/CCGI/multdisp.html


・HTML
  1. <!DOCTYPE html>  
  2. <html>  
  3.   <head>複数ファイルのアップロード  
  4.  <meta charset="utf-8">  
  5.   </head>  
  6.   
  7.   <body>  
  8.  <form action="upload.fcgi" method="POST" enctype="multipart/form-data">  
  9.    <input type="file" name="files" multiple>  
  10.    <input type="submit" value="submit">  
  11.  </form>  
  12.   </body>  
  13. </html>  

・C++ソースコードの説明


・12~85行目:Requestがアップロードかを判定

環境変数[CONTENT_TYPE]の文字列から
  • [multipart/form-data]が含まれているか
  • [boundary]の文字列を取得できるか
を調べてRequestがアップロードかを判定しています。


例としてGoogle ChromeでアップロードをするとCONTENT_TYPEは、以下のようになります。
(WebKitFormBoundary7uUEXkjnVYxPBKMIは、ユニークな値がはいります。)

multipart/form-data; boundary=----WebKitFormBoundary7uUEXkjnVYxPBKMI, referer: 呼び出し元のURL

boundaryの文字列は、アップロードされた各ファイルの本体と、フッタの区切りに使うので覚えておきます。


・90~123行目:ファイルの中身まで標準入力を進める

アップロードされたファイルは、標準入力stdinから以下の順で取得できます。
(複数ファイルがアップロードされた時は、この順に繰り返されます。)
  1. ヘッダ
  2. ファイル本体
  3. フッタ

例えば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] 


ヘッダの構成は以下のようになっています。
  1.  [--]+[CONTENT_TYPEのboundary]で指定されるユニークな文字列[\r][\n]
  2.  Content-Disposition[\r][\n]
  3.  Content-Type[\r][\n][\r][\n]

[\r][\n][\r][\n]の後からがファイル本体になるので、そこまでスキップしています。

送信されたフォームのフィールド名やファイル名などを取得する際には、2の[Content-Disposition]から取得する必要がありますが、本記事では行っていません。


・136~174行目

ファイル本体とヘッダをstdinから取得し、HTMLとし標準出力に表示しています。
また、stdinから文字を取得する際に[boundary]の文字列と比較し、ヘッダは表示せずに終了します。


・C++

  1. #include <string>  
  2. #include <stdio.h>  
  3. #include <iostream>  
  4. #include <stdlib.h> // getenv  
  5.   
  6. const int BOUNDARY_LENGTH = 8; // boundaryの文字数  
  7. using namespace std;  
  8.   
  9. // ========================================================  
  10. //   Requestがアップロードか調べ、boundaryの文字列を返します。  
  11. // ========================================================  
  12. bool isUploadRequest( std::string & boundaryStr )  
  13. {  
  14.  /* 
  15.    環境変数[REQUEST_METHOD]に送信されたメソッド[GET/POST]が入ります。 
  16.    アップロードは、[POST]で送信されてきます。 
  17.  */  
  18.  std::string request_method = getenv( "REQUEST_METHOD" );  
  19.  if ( request_method != "POST" )   
  20.  {  
  21.   fprintf( stderr, "REQUEST_METHOD isn't POST.\n" );  
  22.   return false;  
  23.  }  
  24.   
  25.  /* 
  26.    Uploadでは、環境変数[CONTENT_TYPE]に[multipart/form-data]が含まれています。 
  27.  */  
  28.  std::string content_type = getenv( "CONTENT_TYPE" );  
  29.  if ( content_type.empty() ||   
  30.    content_type.find( "multipart/form-data" ) == std::string::npos )  
  31.  {  
  32.   fprintf( stderr, "CONTENT_TYPE is error.\n" );  
  33.   return false;  
  34.  }  
  35.   
  36.  fprintf( stderr, "%s\n", content_type.c_str());  
  37.   
  38.  /* 
  39.    boundaryを取得します。 
  40.   */  
  41.  std::string::size_type index = content_type.find( "boundary" );  
  42.  if ( index == std::string::npos )  
  43.  {  
  44.   return false;  
  45.  }  
  46.   
  47.  // boundaryの文字列より後に[=]があるかを調べます。  
  48.  index = content_type.find( "=", index + BOUNDARY_LENGTH );  
  49.  if ( index == std::string::npos )  
  50.  {  
  51.   return false;  
  52.  }  
  53.   
  54.  // [=]の後からboundaryを取得します。  
  55.  index++;  
  56.  if ( index >= content_type.size())  
  57.  {  
  58.   return false;  
  59.  }  
  60.   
  61.  std::string::size_type end_index = std::string::npos;  
  62.  if ( content_type[ index ] == '"' )  
  63.  {  
  64.   // [boundary="XXXXXX"]形式は、["]内の文字列を取得します。  
  65.   end_index = content_type.find( '"', index + 1 );  
  66.   if ( end_index == std::string::npos )  
  67.   {  
  68.    return false;  
  69.   }  
  70.   index++;  
  71.  }  
  72.  else  
  73.  {  
  74.   // [boundary=XXXXX, XXXX=XXXX]形式は、[,]までの文字列を取得します。  
  75.   end_index = content_type.find( ',', index + 1 );  
  76.  }  
  77.   
  78.  /* 
  79.    ファイル本体とフッタの区切り文字列を作成します。 
  80.    区切りの文字列は、[\r\n--]+[boundaryの文字列]の形式です。 
  81.  */  
  82.  boundaryStr = "\r\n--";  
  83.  boundaryStr += content_type.substr( index, end_index - index );  
  84.  return true;  
  85. }  
  86.   
  87. // ========================================================  
  88. // ファイルの中身の直前には、[\r\n\r\n]があるので、そこまで進めます。   
  89. // ========================================================  
  90. bool skipHeader()  
  91. {  
  92.  char buf;  
  93.  while ( 1 )  
  94.  {  
  95.   if ( fread( &buf, 1, 1, stdin ) != 1 )  
  96.    return false;  
  97.   
  98.   if ( buf != '\r' )  
  99.    continue;  
  100.   
  101.   if ( fread( &buf, 1, 1, stdin ) != 1 )  
  102.    return false;  
  103.     
  104.   if ( buf != '\n' )  
  105.    continue;  
  106.   
  107.   if ( fread( &buf, 1, 1, stdin ) != 1 )  
  108.    return false;  
  109.     
  110.   if ( buf != '\r' )  
  111.    continue;  
  112.   
  113.   if ( fread( &buf, 1, 1, stdin ) != 1 )  
  114.    return false;  
  115.     
  116.   if ( buf == '\n' )  
  117.   {  
  118.    // [\r\n\r\n]が見つかったので、ループを抜けます。  
  119.    return true;  
  120.   }  
  121.  }  
  122.  return false;  
  123. }  
  124.    
  125. // ========================================================  
  126. // アップロードされたファイルの中身を表示します。  
  127. // ========================================================  
  128. bool printFile( const std::string & boundaryStr )  
  129. {  
  130.  // ヘッダ部分をスキップし、ファイルの中身まで進めます。  
  131.  if ( !skipHeader())  
  132.  {  
  133.   return false;  
  134.  }  
  135.   
  136.  // ファイルの中身を表示します。  
  137.  char buf;  
  138.  string tmpStr;  
  139.  string::size_type boundaryLength = boundaryStr.size();  
  140.  string::size_type index          = 0;  
  141.   
  142.  while ( 1 )   
  143.  {  
  144.   if ( fread( &buf, 1, 1, stdin ) != 1 )  
  145.    return true;  
  146.   
  147.   if ( buf == boundaryStr[ index ] )  
  148.   {  
  149.    /* 
  150.      boundaryの文字なので、一旦tmpStrに保存し、比較するboundaryのインデックスを進めます。 
  151.    */  
  152.    index++;  
  153.    tmpStr += buf;  
  154.   
  155.    if ( index >= boundaryLength )  
  156.    {  
  157.     // boundaryの文字が全て見つかったので、ループを抜けます。  
  158.     break;  
  159.    }  
  160.   }  
  161.   else  
  162.   {  
  163.    if ( index > 0 )  
  164.    {  
  165.     // boundaryでない文字列が見つかったので、tmpStrの文字列を表示しインデックスを0に戻します。  
  166.     printf( "%s", tmpStr.c_str());  
  167.     index = 0;  
  168.     tmpStr.clear();  
  169.    }  
  170.   
  171.    putchar( buf );  
  172.   }  
  173.  }  
  174.  return true;  
  175. }  
  176.   
  177. // ========================================================  
  178. // main  
  179. // ========================================================  
  180. int main()  
  181. {  
  182.  // Requestsがアップロードかを調べます。  
  183.  std::string boundaryStr;  
  184.  if ( !isUploadRequest( boundaryStr ))  
  185.  {  
  186.   return -1;  
  187.  }  
  188.    
  189.   
  190.  // HTMLを出力します。  
  191.  printf( "Content-Type: text/html\n\n" );  
  192.    
  193.  printf( "<html>" );  
  194.  printf( "<head><title>test</title></head>" );  
  195.  printf( "<body>" );  
  196.   
  197.  std::string boundary;  
  198.  if ( !isUploadRequest( boundary ))  
  199.  {  
  200.   printf( "error\n" );  
  201.  }  
  202.  else  
  203.  {  
  204.   while ( 1 )  
  205.   {  
  206.    if ( !printFile( boundaryStr )) break;  
  207.   }  
  208.  }  
  209.  printf( "</body>" );  
  210.  printf( "</html>" );   
  211.  return 0;  
  212. }  

0 コメント:

コメントを投稿

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Blogger Templates