public WebDav(Logger logger, WebDavDb webDavDb, Target target, Document document, string urlStr, string depthStr, ContentType contentType, bool useEtag) { _logger = logger; _webDavDb = webDavDb; _document = document; _webDavKind = target.WebDavKind; _targetKind = target.TargetKind; _contentType = contentType; _useEtag = useEtag; if (depthStr != null) { if (depthStr == "0") { _depth = Depth.Depth0; } else if (depthStr == "1") { _depth = Depth.Depth1; } else if (depthStr == "infinity") { _depth = Depth.DepthInfinity; } } _fullPath = target.FullPath; _hrefHost = urlStr + target.Uri; //hrefをhttp://hostname と uri部分に分解する var index = _hrefHost.IndexOf("://"); if (index != -1) { _hrefUri = _hrefHost.Substring(index + 3); var pos = _hrefUri.IndexOf('/'); if (pos != -1) { _hrefUri = _hrefUri.Substring(pos); _hrefHost = _hrefHost.Substring(0, index + pos + 3); } } if (_hrefUri != "") { if (_targetKind == TargetKind.Dir && _hrefUri[_hrefUri.Length - 1] != '/') _hrefUri = _hrefUri + "/"; } //RFC 2518(5.2) コレクションに対するリクエストで最後に/(スラッシュ)なし //で参照されるとき自動的にこれを付加して処理することができる //この際、Content-Locationで見なしたURLをクライアントに返すべき //document.AddHeader("Content-Location",hrefHost+Util.SwapStr("%2f","/",HttpUtility.UrlEncode(hrefUri))); //document.AddHeader("Content-Location",HttpUtility.UrlPathEncode(hrefHost + hrefUri)); var href = Uri.EscapeDataString(_hrefUri); //Ver5.4.6 href = Util.SwapStr("%2F", "/", href); href = Util.SwapStr("%2f", "/", href); href = Util.SwapStr("+", "%20", href); document.AddHeader("Content-Location", _hrefHost + href); }
//接続単位の処理 protected override void OnSubThread(SockObj sockObj) { Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); var sockTcp = (SockTcp)sockObj; var remoteIp = sockTcp.RemoteIp; //opBase 及び loggerはバーチャルホストで変更されるので、 //このポインタを初期化に使用できない bool keepAlive = true;//レスポンスが終了したとき接続を切断しないで継続する //1回目の通信でバーチャルホストの検索を実施する var checkVirtual = true; var request = new Request(Logger,sockTcp);//リクエストライン処理クラス //受信ヘッダ var recvHeader = new Header(); //Ver5.1.x string urlStr = null;//http://example.com //接続が継続している間は、このループの中にいる(継続か否かをkeepAliveで保持する) //「continue」は、次のリクエストを待つ 「break」は、接続を切断する事を意味する WebStream inputStream = null; var outputStream = new WebStream(-1); while (keepAlive && IsLife()) { int responseCode; //*************************************************************** // ドキュメント生成クラスの初期化 //*************************************************************** var contentType = new ContentType(Conf); var document = new Document(Kernel, Logger, Conf, sockTcp, contentType); var authrization = new Authorization(Conf, Logger); var authName = ""; //*************************************************************** //データ取得 //*************************************************************** //リクエスト取得 //ここのタイムアウト値は、大きすぎるとブラウザの切断を取得できないでブロックしてしまう var requestStr = sockTcp.AsciiRecv(Timeout, this); if (requestStr == null) break; //\r\nの削除 requestStr = Inet.TrimCrlf(requestStr); //Ver5.8.8 リクエストの解釈に失敗した場合に、処理を中断する //request.Init(requestStr); if (!request.Init(requestStr)){ break; } //ヘッダ取得(内部データは初期化される) if (!recvHeader.Recv(sockTcp,(int)Conf.Get("timeOut"),this)) break; { //Ver5.1.x var hostStr = recvHeader.GetVal("host"); urlStr = hostStr==null ? null : string.Format("{0}://{1}",(ssl != null)?"https":"http",hostStr); } //入力取得(POST及びPUTの場合) var contentLengthStr = recvHeader.GetVal("Content-Length"); if(contentLengthStr != null) { try{ //max,lenはともにlong var max = Convert.ToInt64(contentLengthStr); if(max!=0){//送信データあり inputStream = new WebStream((256000<max)?-1:(int)max); var errorCount = 0; while(inputStream.Length<max && IsLife()){ var len = max - inputStream.Length; if (len > 51200000) { len = 51200000; } var b = sockTcp.Recv((int)len, (int)Conf.Get("timeOut"),this); if (!inputStream.Add(b)) { errorCount++;//エラー蓄積 Logger.Set(LogKind.Error, null, 41, string.Format("content-Length={0} Recv={1}", max, inputStream.Length)); } else { errorCount = 0;//初期化 } Logger.Set(LogKind.Detail, null,38, string.Format("Content-Length={0} {1}bytes Received.", max, inputStream.Length)); if (errorCount > 5){//5回連続して受信が無かった場合、サーバエラー responseCode = 500; goto SEND;//サーバエラー } Thread.Sleep(10); } Logger.Set(LogKind.Detail, null, 39, string.Format("Content-Length={0} {1}bytes", max, inputStream.Length)); } }catch(Exception ex){ Logger.Set(LogKind.Error, null, 40, ex.Message); } } // /によるパラメータ渡しに対応 //for (int i = 0;i < Option->CgiExt->Count;i++) { // wsprintf(TmpBuf,".%s/",Option->CgiExt->Strings[i]); // strupr(TmpBuf); // strcpy(Buf,Headers->Uri); // strupr(Buf); // if (NULL != (p = strstr(Buf,TmpBuf))) { // i = p - Buf; // i += strlen(TmpBuf) - 1; // p = &Headers->Uri[i]; // *p = '\0'; // p = &Headers->UriNoConversion[i]; // *p = '\0'; // wsprintf(TmpBuf,"/%s",p + 1); // Headers->PathInfo = new char[strlen(TmpBuf) + 1]; // strcpy(Headers->PathInfo,TmpBuf); // break; // } //} //*************************************************************** //バーチャルホストの検索を実施し、opBase、logger及び webDavDb を置き換える //*************************************************************** if (checkVirtual) {//初回のみ ReplaceVirtualHost(recvHeader.GetVal("host"),sockTcp.LocalAddress.Address,sockTcp.LocalAddress.Port); checkVirtual = false; } //*************************************************************** //接続を継続するかどうかの判断 keepAliveの初期化 //*************************************************************** if (ssl != null) { keepAlive = false;//SSL通信では、1回づつコネクションが必要 }else{ if (request.Ver == "HTTP/1.1") {//HTTP1.1はデフォルトで keepAlive=true keepAlive = true; } else { // HTTP/1.1以外の場合、継続接続は、Connection: Keep-Aliveの有無に従う keepAlive = recvHeader.GetVal("Connection") == "Keep-Alive"; } } //*************************************************************** // ドキュメント生成クラスの初期化 //*************************************************************** //var contentType = new ContentType(OneOption); //var document = new Document(kernel,Logger,OneOption,sockTcp,contentType); //*************************************************************** // ログ //*************************************************************** Logger.Set(LogKind.Normal, sockTcp, ssl != null ? 23 : 24, request.LogStr); //*************************************************************** // 認証 //*************************************************************** //var authrization = new Authorization(OneOption,Logger); //string authName = ""; if (!authrization.Check(request.Uri, recvHeader.GetVal("authorization"), ref authName)) { responseCode = 401; keepAlive = false;//切断 goto SEND; } //*************************************************************** // 不正なURIに対するエラー処理 //*************************************************************** //URIを点検して不正な場合はエラーコードを返す responseCode = CheckUri(sockTcp, request, recvHeader); if (responseCode != 200) { keepAlive = false;//切断 goto SEND; } //*************************************************************** //ターゲットオブジェクトの初期化 //*************************************************************** var target = new Target(Conf,Logger); if (target.DocumentRoot == null) { Logger.Set(LogKind.Error,sockTcp,14,string.Format("documentRoot={0}",Conf.Get("documentRoot")));//ドキュメントルートで指定されたフォルダが存在しません(処理を継続できません) break;//ドキュメントルートが無効な場合は、処理を継続できない } target.InitFromUri(request.Uri); //*************************************************************** // 送信ヘッダの追加 //*************************************************************** // 特別拡張 BlackJumboDog経由のリクエストの場合 送信ヘッダにRemoteHostを追加する if ((bool)Conf.Get("useExpansion")) { if (recvHeader.GetVal("Host") != null) { document.AddHeader("RemoteHost",sockTcp.RemoteAddress.Address.ToString()); } } //受信ヘッダに「PathInfo:」が設定されている場合、送信ヘッダに「PathTranslated」を追加する var pathInfo = recvHeader.GetVal("PathInfo"); if (pathInfo != null) { pathInfo = target.DocumentRoot + pathInfo; document.AddHeader("PathTranslated",Util.SwapChar('/','\\',pathInfo)); } //*************************************************************** //メソッドに応じた処理 OPTIONS 対応 Ver5.1.x //*************************************************************** if(WebDav.IsTarget(request.Method)){ var webDav = new WebDav(Logger, _webDavDb, target, document, urlStr, recvHeader.GetVal("Depth"), contentType,(bool)Conf.Get("useEtag")); var inputBuf = new byte[0]; if(inputStream!=null){ inputBuf = inputStream.GetBytes(); } switch(request.Method) { case HttpMethod.Options: responseCode = webDav.Option(); break; case HttpMethod.Delete: responseCode = webDav.Delete(); break; case HttpMethod.Put: responseCode = webDav.Put(inputBuf); break; case HttpMethod.Proppatch: responseCode = webDav.PropPatch(inputBuf); break; case HttpMethod.Propfind: responseCode = webDav.PropFind(); break; case HttpMethod.Mkcol: responseCode = webDav.MkCol(); break; case HttpMethod.Copy: case HttpMethod.Move: responseCode = 405; //Destnationで指定されたファイルは書き込み許可されているか? var dstTarget = new Target(Conf,Logger); string destinationStr = recvHeader.GetVal("Destination"); if(destinationStr != null) { if(destinationStr.IndexOf("://") == -1) { destinationStr = urlStr + destinationStr; } var uri = new Uri(destinationStr); dstTarget.InitFromUri(uri.LocalPath); if(dstTarget.WebDavKind == WebDavKind.Write) { var overwrite = false; var overwriteStr = recvHeader.GetVal("Overwrite"); if(overwriteStr != null) { if(overwriteStr == "F") { overwrite = true; } } responseCode = webDav.MoveCopy(dstTarget,overwrite,request.Method); document.AddHeader("Location",destinationStr); } } break; } //WebDAVに対するリクエストは、ここで処理完了 goto SEND; } //以下 label SENDまでの間は、GET/POSTに関する処理 //*************************************************************** //ターゲットの種類に応じた処理 //*************************************************************** if (target.TargetKind == TargetKind.Non) { //見つからない場合 responseCode = 404; goto SEND; } if (target.TargetKind == TargetKind.Move) { //ターゲットはディレクトリの場合 responseCode = 301; goto SEND; } if (target.TargetKind == TargetKind.Dir) { //ディレクトリ一覧表示の場合 //インデックスドキュメントを生成する if (!document.CreateFromIndex(request, target.FullPath)) break; goto SEND; } //*************************************************************** // 隠し属性のファイルへのアクセス制御 //*************************************************************** if (!(bool)Conf.Get("useHidden")) { if ((target.Attr & FileAttributes.Hidden) == FileAttributes.Hidden) { //エラーキュメントを生成する responseCode = 404; keepAlive = false;//切断 goto SEND; } } if (target.TargetKind == TargetKind.Cgi || target.TargetKind == TargetKind.Ssi) { keepAlive = false;//デフォルトで切断 //環境変数作成 var env = new Env(Kernel,Conf,request, recvHeader,sockTcp, target.FullPath); // 詳細ログ Logger.Set(LogKind.Detail,sockTcp,18,string.Format("{0} {1}",target.CgiCmd,Path.GetFileName(target.FullPath))); if (target.TargetKind == TargetKind.Cgi) { var cgi = new Cgi(); var cgiTimeout = (int)Conf.Get("cgiTimeout"); if (!cgi.Exec(target,request.Param,env,inputStream,out outputStream,cgiTimeout)) { // エラー出力 var errStr = Encoding.ASCII.GetString(outputStream.GetBytes()); Logger.Set(LogKind.Error,sockTcp,16,errStr); responseCode = 500; goto SEND; } //*************************************************** // NPH (Non-Parsed Header CGI)スクリプト nph-で始まる場合、サーバ処理(レスポンスコードやヘッダの追加)を経由しない //*************************************************** if (Path.GetFileName(target.FullPath).IndexOf("nph-") == 0) { sockTcp.SendUseEncode(outputStream.GetBytes());//CGI出力をそのまま送信する break; } // CGIで得られた出力から、本体とヘッダを分離する if(!document.CreateFromCgi(outputStream.GetBytes())) break; // cgi出力で、Location:が含まれる場合、レスポンスコードを302にする if (document.SearchLocation())//Location:ヘッダを含むかどうか responseCode = 302; goto SEND; } //SSI var ssi = new Ssi(Kernel, Logger,Conf, sockTcp, request, recvHeader); if (!ssi.Exec(target,env,outputStream)) { // エラー出力 Logger.Set(LogKind.Error,sockTcp,22,MLang.GetString(outputStream.GetBytes())); responseCode = 500; goto SEND; } document.CreateFromSsi(outputStream.GetBytes(),target.FullPath); goto SEND; } //以下は、通常ファイルの処理 TARGET_KIND.FILE //******************************************************************** //Modified処理 //******************************************************************** if (recvHeader.GetVal("If_Modified_Since") != null) { var dt = Util.Str2Time(recvHeader.GetVal("If-Modified-Since")); if (target.FileInfo.LastWriteTimeUtc.Ticks / 10000000 <= dt.Ticks / 10000000) { responseCode = 304; goto SEND; } } if (recvHeader.GetVal("If_Unmodified_Since") != null) { var dt = Util.Str2Time(recvHeader.GetVal("If_Unmodified_Since")); if (target.FileInfo.LastWriteTimeUtc.Ticks / 10000000 > dt.Ticks / 10000000) { responseCode = 412; goto SEND; } } document.AddHeader("Last-Modified",Util.UtcTime2Str(target.FileInfo.LastWriteTimeUtc)); //******************************************************************** //ETag処理 //******************************************************************** // (1) useEtagがtrueの場合は、送信時にETagを付加する // (2) If-None-Match 若しくはIf-Matchヘッダが指定されている場合は、排除対象かどうかの判断が必要になる if ((bool)Conf.Get("useEtag") || recvHeader.GetVal("If-Match") != null || recvHeader.GetVal("If-None-Match") != null) { //Ver5.1.5 //string etagStr = string.Format("\"{0:x}-{1:x}\"", target.FileInfo.Length, (target.FileInfo.LastWriteTimeUtc.Ticks / 10000000)); var etagStr = WebServerUtil.Etag(target.FileInfo); string str; if (null != (str = recvHeader.GetVal("If-Match"))) { if (str != "*" && str != etagStr) { responseCode = 412; goto SEND; } } if (null != (str = recvHeader.GetVal("If-None-Match"))) { if (str != "*" && str == etagStr) { responseCode = 304; goto SEND; } } if ((bool)Conf.Get("useEtag")) document.AddHeader("ETag",etagStr); } //******************************************************************** //Range処理 //******************************************************************** document.AddHeader("Accept-Range","bytes"); var rangeFrom = 0L;//デフォルトは最初から var rangeTo = target.FileInfo.Length;//デフォルトは最後まで(ファイルサイズ) if (recvHeader.GetVal("Range") != null) {//レンジ指定のあるリクエストの場合 var range = recvHeader.GetVal("Range"); //指定範囲を取得する(マルチ指定には未対応) if (range.IndexOf("bytes=") == 0) { range = range.Substring(6); var tmp = range.Split('-'); //Ver5.3.5 ApacheKiller対処 if (tmp.Length > 20) { Logger.Set(LogKind.Secure, sockTcp,9000054, string.Format("[ Apache Killer ]Range:{0}", range)); AutoDeny(false, remoteIp); responseCode = 503; keepAlive = false;//切断 goto SEND; } if(tmp.Length == 2) { //Ver5.3.6 のデバッグ用 //tmp[1] = "499"; if(tmp[0] != "") { if(tmp[1] != "") {// bytes=0-10 0~10の11バイト //Ver5.5.9 rangeFrom = Convert.ToInt64(tmp[0]); if (tmp[1] != "") { //Ver5.5.9 rangeTo = Convert.ToInt64(tmp[1]); if (target.FileInfo.Length <= rangeTo) { rangeTo = target.FileInfo.Length - 1; } else { document.SetRangeTo = true;//Ver5.4.0 } } } else {// bytes=3- 3~最後まで rangeTo = target.FileInfo.Length - 1; rangeFrom = Convert.ToInt64(tmp[0]); } } else { if(tmp[1] != "") {// bytes=-3 最後から3バイト var len = Convert.ToInt64(tmp[1]); rangeTo = target.FileInfo.Length - 1; rangeFrom = rangeTo-len+1; if(rangeFrom<0) rangeFrom=0; document.SetRangeTo = true;//Ver5.4.0 } } if(rangeFrom <= rangeTo) { //正常に範囲を取得できた場合、事後Rangeモードで動作する document.AddHeader("Content-Range",string.Format("bytes {0}-{1}/{2}",rangeFrom,rangeTo,target.FileInfo.Length)); responseCode = 206; } } } } //通常ファイルのドキュメント if (request.Method != HttpMethod.Head) { if (!document.CreateFromFile(target.FullPath,rangeFrom,rangeTo)) break; } SEND: //レスポンスコードが200以外の場合は、ドキュメント(及び送信ヘッダ)をエラー用に変更する if(responseCode != 200 && responseCode != 302 && responseCode != 206 && responseCode != 207 && responseCode != 204 && responseCode != 201) { //ResponceCodeの応じてエラードキュメントを生成する if (!document.CreateFromErrorCode(request,responseCode)) break; if (responseCode == 301) {//ターゲットがファイルではなくディレクトの間違いの場合 if(urlStr != null) { var str = string.Format("{0}{1}/",urlStr,request.Uri); document.AddHeader("Location",Encoding.UTF8.GetBytes(str)); } } if (responseCode == 304 || responseCode == 301) {//304 or 301 の場合は、ヘッダのみになる document.Clear(); } else { if (responseCode == 401) { document.AddHeader("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", authName)); } } } //Ver5.6.2 request.Send()廃止 var responseStr = request.CreateResponse(responseCode); sockTcp.AsciiSend(responseStr);//レスポンス送信 Logger.Set(LogKind.Detail, sockTcp, 4, responseStr);//ログ document.Send(keepAlive,this);//ドキュメント本体送信 } if(inputStream!=null) inputStream.Dispose(); if (outputStream != null) outputStream.Dispose(); //end://このソケット接続の終了 if (sockTcp != null) { sockTcp.Close(); } }