1 #include "..\..\zlib\zlib.h"
8 #include "WebServerSocket.h"
9 #include "WebClientSocket.h"
10 #include "WebServer.h"
13 CAtlStringMap
<CWebServer::RequestHandler
> CWebServer::m_internalpages
;
14 CAtlStringMap
<UINT
> CWebServer::m_downloads
;
15 CAtlStringMap
<CStringA
, CStringA
> CWebServer::m_mimes
;
17 CWebServer::CWebServer(CMainFrame
* pMainFrame
, int nPort
)
18 : m_pMainFrame(pMainFrame
)
21 if(m_internalpages
.IsEmpty())
23 m_internalpages
[_T("/")] = &CWebClientSocket::OnIndex
;
24 m_internalpages
[_T("/index.html")] = &CWebClientSocket::OnIndex
;
25 m_internalpages
[_T("/browser.html")] = &CWebClientSocket::OnBrowser
;
26 m_internalpages
[_T("/controls.html")] = &CWebClientSocket::OnControls
;
27 m_internalpages
[_T("/command.html")] = &CWebClientSocket::OnCommand
;
28 m_internalpages
[_T("/status.html")] = &CWebClientSocket::OnStatus
;
29 m_internalpages
[_T("/player.html")] = &CWebClientSocket::OnPlayer
;
30 m_internalpages
[_T("/snapshot.jpg")] = &CWebClientSocket::OnSnapShotJpeg
;
31 m_internalpages
[_T("/404.html")] = &CWebClientSocket::OnError404
;
32 m_internalpages
[_T("/convres.html")] = &CWebClientSocket::OnConvRes
;
35 if(m_downloads
.IsEmpty())
37 m_downloads
[_T("/default.css")] = IDF_DEFAULT_CSS
;
38 m_downloads
[_T("/vbg.gif")] = IDF_VBR_GIF
;
39 m_downloads
[_T("/vbs.gif")] = IDF_VBS_GIF
;
40 m_downloads
[_T("/sliderbar.gif")] = IDF_SLIDERBAR_GIF
;
41 m_downloads
[_T("/slidergrip.gif")] = IDF_SLIDERGRIP_GIF
;
42 m_downloads
[_T("/sliderback.gif")] = IDF_SLIDERBACK_GIF
;
43 m_downloads
[_T("/1pix.gif")] = IDF_1PIX_GIF
;
44 m_downloads
[_T("/headericon.png")] = IDF_HEADERICON_PNG
;
45 m_downloads
[_T("/headerback.png")] = IDF_HEADERBACK_PNG
;
46 m_downloads
[_T("/headerclose.png")] = IDF_HEADERCLOSE_PNG
;
47 m_downloads
[_T("/leftside.png")] = IDF_LEFTSIDE_PNG
;
48 m_downloads
[_T("/rightside.png")] = IDF_RIGHTSIDE_PNG
;
49 m_downloads
[_T("/bottomside.png")] = IDF_BOTTOMSIDE_PNG
;
50 m_downloads
[_T("/leftbottomside.png")] = IDF_LEFTBOTTOMSIDE_PNG
;
51 m_downloads
[_T("/rightbottomside.png")] = IDF_RIGHTBOTTOMSIDE_PNG
;
52 m_downloads
[_T("/seekbarleft.png")] = IDF_SEEKBARLEFT_PNG
;
53 m_downloads
[_T("/seekbarmid.png")] = IDF_SEEKBARMID_PNG
;
54 m_downloads
[_T("/seekbarright.png")] = IDF_SEEKBARRIGHT_PNG
;
55 m_downloads
[_T("/seekbargrip.png")] = IDF_SEEKBARGRIP_PNG
;
56 m_downloads
[_T("/logo.png")] = IDF_LOGO7
;
57 m_downloads
[_T("/controlback.png")] = IDF_CONTROLBACK_PNG
;
58 m_downloads
[_T("/controlbuttonplay.png")] = IDF_CONTROLBUTTONPLAY_PNG
;
59 m_downloads
[_T("/controlbuttonpause.png")] = IDF_CONTROLBUTTONPAUSE_PNG
;
60 m_downloads
[_T("/controlbuttonstop.png")] = IDF_CONTROLBUTTONSTOP_PNG
;
61 m_downloads
[_T("/controlbuttonskipback.png")] = IDF_CONTROLBUTTONSKIPBACK_PNG
;
62 m_downloads
[_T("/controlbuttondecrate.png")] = IDF_CONTROLBUTTONDECRATE_PNG
;
63 m_downloads
[_T("/controlbuttonincrate.png")] = IDF_CONTROLBUTTONINCRATE_PNG
;
64 m_downloads
[_T("/controlbuttonskipforward.png")] = IDF_CONTROLBUTTONSKIPFORWARD_PNG
;
65 m_downloads
[_T("/controlbuttonstep.png")] = IDF_CONTROLBUTTONSTEP_PNG
;
66 m_downloads
[_T("/controlvolumeon.png")] = IDF_CONTROLVOLUMEON_PNG
;
67 m_downloads
[_T("/controlvolumeoff.png")] = IDF_CONTROLVOLUMEOFF_PNG
;
68 m_downloads
[_T("/controlvolumebar.png")] = IDF_CONTROLVOLUMEBAR_PNG
;
69 m_downloads
[_T("/controlvolumegrip.png")] = IDF_CONTROLVOLUMEGRIP_PNG
;
73 CString
str(_T("MIME\\Database\\Content Type"));
74 if(ERROR_SUCCESS
== key
.Open(HKEY_CLASSES_ROOT
, str
, KEY_READ
))
77 DWORD len
= countof(buff
);
78 for(int i
= 0; ERROR_SUCCESS
== key
.EnumKey(i
, buff
, &len
); i
++, len
= countof(buff
))
82 ULONG len
= countof(ext
);
83 if(ERROR_SUCCESS
== mime
.Open(HKEY_CLASSES_ROOT
, str
+ _T("\\") + buff
, KEY_READ
)
84 && ERROR_SUCCESS
== mime
.QueryStringValue(_T("Extension"), ext
, &len
))
85 m_mimes
[CStringA(ext
).MakeLower()] = CStringA(buff
).MakeLower();
89 m_mimes
[".html"] = "text/html";
90 m_mimes
[".txt"] = "text/plain";
91 m_mimes
[".css"] = "text/css";
92 m_mimes
[".gif"] = "image/gif";
93 m_mimes
[".jpeg"] = "image/jpeg";
94 m_mimes
[".jpg"] = "image/jpeg";
95 m_mimes
[".png"] = "image/png";
97 GetModuleFileName(AfxGetInstanceHandle(), str
.GetBuffer(MAX_PATH
), MAX_PATH
);
99 m_webroot
= CPath(str
);
100 m_webroot
.RemoveFileSpec();
102 CString WebRoot
= AfxGetAppSettings().WebRoot
;
103 WebRoot
.Replace('/', '\\');
106 if(WebRoot
.Find(_T(":\\")) < 0 && WebRoot
.Find(_T("\\\\")) < 0) m_webroot
.Append(WebRoot
);
108 m_webroot
.Canonicalize();
109 m_webroot
.MakePretty();
110 if(!m_webroot
.IsDirectory()) m_webroot
= CPath();
112 CAtlList
<CString
> sl
;
113 Explode(AfxGetAppSettings().WebServerCGI
, sl
, ';');
114 POSITION pos
= sl
.GetHeadPosition();
117 CAtlList
<CString
> sl2
;
118 CString ext
= Explode(sl
.GetNext(pos
), sl2
, '=', 2);
119 if(sl2
.GetCount() < 2) continue;
120 m_cgi
[ext
] = sl2
.GetTail();
124 m_hThread
= ::CreateThread(NULL
, 0, StaticThreadProc
, (LPVOID
)this, 0, &m_ThreadId
);
127 CWebServer::~CWebServer()
129 if(m_hThread
!= NULL
)
131 PostThreadMessage(m_ThreadId
, WM_QUIT
, 0, 0);
132 WaitForSingleObject(m_hThread
, 10000);
133 EXECUTE_ASSERT(CloseHandle(m_hThread
));
137 DWORD WINAPI
CWebServer::StaticThreadProc(LPVOID lpParam
)
139 return ((CWebServer
*)lpParam
)->ThreadProc();
142 DWORD
CWebServer::ThreadProc()
144 if(!AfxSocketInit(NULL
))
147 CWebServerSocket
s(this, m_nPort
);
150 while((int)GetMessage(&msg
, NULL
, 0, 0) > 0)
152 TranslateMessage(&msg
);
153 DispatchMessage(&msg
);
159 static void PutFileContents(LPCTSTR fn
, const CStringA
& data
)
161 if(FILE* f
= _tfopen(fn
, _T("wb")))
163 fwrite((LPCSTR
)data
, 1, data
.GetLength(), f
);
168 void CWebServer::Deploy(CString dir
)
171 if(LoadResource(IDR_HTML_INDEX
, data
, RT_HTML
)) PutFileContents(dir
+ _T("index.html"), data
);
172 if(LoadResource(IDR_HTML_BROWSER
, data
, RT_HTML
)) PutFileContents(dir
+ _T("browser.html"), data
);
173 if(LoadResource(IDR_HTML_CONTROLS
, data
, RT_HTML
)) PutFileContents(dir
+ _T("controls.html"), data
);
174 if(LoadResource(IDR_HTML_404
, data
, RT_HTML
)) PutFileContents(dir
+ _T("404.html"), data
);
175 if(LoadResource(IDR_HTML_PLAYER
, data
, RT_HTML
)) PutFileContents(dir
+ _T("player.html"), data
);
177 POSITION pos
= m_downloads
.GetStartPosition();
182 m_downloads
.GetNextAssoc(pos
, fn
, id
);
183 if(LoadResource(id
, data
, _T("FILE")))
184 PutFileContents(dir
+ fn
, data
);
188 bool CWebServer::ToLocalPath(CString
& path
, CString
& redir
)
190 if(!path
.IsEmpty() && m_webroot
.IsDirectory())
193 str
.Replace('/', '\\');
197 p
.Combine(m_webroot
, str
);
202 CAtlList
<CString
> sl
;
203 Explode(AfxGetAppSettings().WebDefIndex
, sl
, ';');
204 POSITION pos
= sl
.GetHeadPosition();
207 str
= sl
.GetNext(pos
);
214 if(redir
.GetAt(redir
.GetLength()-1) != '/') redir
+= '/';
221 if(_tcslen(p
) > _tcslen(m_webroot
) && p
.FileExists())
231 bool CWebServer::LoadPage(UINT resid
, CStringA
& str
, CString path
)
234 if(ToLocalPath(path
, redir
))
236 if(FILE* f
= _tfopen(path
, _T("rb")))
239 char* buff
= str
.GetBufferSetLength(ftell(f
));
241 int len
= fread(buff
, 1, str
.GetLength(), f
);
243 return len
== str
.GetLength();
247 return LoadResource(resid
, str
, RT_HTML
);
250 void CWebServer::OnAccept(CWebServerSocket
* pServer
)
252 CAutoPtr
<CWebClientSocket
> p(new CWebClientSocket(this, m_pMainFrame
));
253 if(pServer
->Accept(*p
))
257 if(AfxGetAppSettings().fWebServerLocalhostOnly
&& p
->GetPeerName(name
, port
) && name
!= _T("127.0.0.1"))
263 m_clients
.AddTail(p
);
267 void CWebServer::OnClose(CWebClientSocket
* pClient
)
269 POSITION pos
= m_clients
.GetHeadPosition();
273 if(m_clients
.GetNext(pos
) == pClient
)
275 m_clients
.RemoveAt(cur
);
281 void CWebServer::OnRequest(CWebClientSocket
* pClient
, CStringA
& hdr
, CStringA
& body
)
283 CPath
p(pClient
->m_path
);
284 CStringA ext
= p
.GetExtension().MakeLower();
286 if(ext
.IsEmpty()) mime
= "text/html";
287 else m_mimes
.Lookup(ext
, mime
);
289 hdr
= "HTTP/1.0 200 OK\r\n";
291 bool fHandled
= false, fCGI
= false;
293 if(!fHandled
&& m_webroot
.IsDirectory())
296 fHandled
= fCGI
= CallCGI(pClient
, tmphdr
, body
, mime
);
300 tmphdr
.Replace("\r\n", "\n");
301 CAtlList
<CStringA
> hdrlines
;
302 ExplodeMin(tmphdr
, hdrlines
, '\n');
303 POSITION pos
= hdrlines
.GetHeadPosition();
307 CAtlList
<CStringA
> sl
;
308 CStringA key
= Explode(hdrlines
.GetNext(pos
), sl
, ':', 2);
309 if(sl
.GetCount() < 2) continue;
310 key
.Trim().MakeLower();
311 if(key
== "content-type") {mime
= sl
.GetTail().Trim(); hdrlines
.RemoveAt(cur
);}
312 else if(key
== "content-length") {hdrlines
.RemoveAt(cur
);}
314 tmphdr
= Implode(hdrlines
, '\n');
315 tmphdr
.Replace("\n", "\r\n");
316 hdr
+= tmphdr
+ "\r\n";
320 RequestHandler rh
= NULL
;
321 if(!fHandled
&& m_internalpages
.Lookup(pClient
->m_path
, rh
) && (pClient
->*rh
)(hdr
, body
, mime
))
323 if(mime
.IsEmpty()) mime
= "text/html";
326 if(pClient
->m_get
.Lookup(_T("redir"), redir
)
327 || pClient
->m_post
.Lookup(_T("redir"), redir
))
329 if(redir
.IsEmpty()) redir
= '/';
332 "HTTP/1.0 302 Found\r\n"
333 "Location: " + CStringA(redir
) + "\r\n";
340 if(!fHandled
&& m_webroot
.IsDirectory())
342 fHandled
= LoadPage(0, body
, pClient
->m_path
);
347 if(!fHandled
&& m_downloads
.Lookup(pClient
->m_path
, resid
) && LoadResource(resid
, res
, _T("FILE")))
349 if(mime
.IsEmpty()) mime
= "application/octet-stream";
350 memcpy(body
.GetBufferSetLength(res
.GetLength()), res
.GetBuffer(), res
.GetLength());
356 hdr
= mime
== "text/html"
357 ? "HTTP/1.0 301 Moved Permanently\r\n" "Location: /404.html\r\n"
358 : "HTTP/1.0 404 Not Found\r\n";
362 if(mime
== "text/html" && !fCGI
)
365 "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n"
366 "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n"
367 "Pragma: no-cache\r\n";
370 if(AfxGetAppSettings().fWebServerPrintDebugInfo
)
375 pos
= pClient
->m_hdrlines
.GetStartPosition();
376 while(pos
) {pClient
->m_hdrlines
.GetNextAssoc(pos
, key
, value
); debug
+= "HEADER[" + key
+ "] = " + value
+ "<br>\r\n";}
377 debug
+= "cmd: " + pClient
->m_cmd
+ "<br>\r\n";
378 debug
+= "path: " + pClient
->m_path
+ "<br>\r\n";
379 debug
+= "ver: " + pClient
->m_ver
+ "<br>\r\n";
380 pos
= pClient
->m_get
.GetStartPosition();
381 while(pos
) {pClient
->m_get
.GetNextAssoc(pos
, key
, value
); debug
+= "GET[" + key
+ "] = " + value
+ "<br>\r\n";}
382 pos
= pClient
->m_post
.GetStartPosition();
383 while(pos
) {pClient
->m_post
.GetNextAssoc(pos
, key
, value
); debug
+= "POST[" + key
+ "] = " + value
+ "<br>\r\n";}
384 pos
= pClient
->m_cookie
.GetStartPosition();
385 while(pos
) {pClient
->m_cookie
.GetNextAssoc(pos
, key
, value
); debug
+= "COOKIE[" + key
+ "] = " + value
+ "<br>\r\n";}
386 pos
= pClient
->m_request
.GetStartPosition();
387 while(pos
) {pClient
->m_request
.GetNextAssoc(pos
, key
, value
); debug
+= "REQUEST[" + key
+ "] = " + value
+ "<br>\r\n";}
390 body
.Replace("[path]", CStringA(pClient
->m_path
));
391 body
.Replace("[indexpath]", "/index.html");
392 body
.Replace("[commandpath]", "/command.html");
393 body
.Replace("[browserpath]", "/browser.html");
394 body
.Replace("[controlspath]", "/controls.html");
395 body
.Replace("[wmcname]", "wm_command");
396 body
.Replace("[setposcommand]", CMD_SETPOS
);
397 body
.Replace("[setvolumecommand]", CMD_SETVOLUME
);
398 body
.Replace("[debug]", debug
);
399 // TODO: add more general tags to replace
403 if(AfxGetAppSettings().fWebServerUseCompression
&& hdr
.Find("Content-Encoding:") < 0)
406 CString accept_encoding
;
407 pClient
->m_hdrlines
.Lookup(_T("accept-encoding"), accept_encoding
);
408 accept_encoding
.MakeLower();
409 CAtlList
<CString
> sl
;
410 ExplodeMin(accept_encoding
, sl
, ',');
411 if(!sl
.Find(_T("gzip"))) break;;
413 CHAR path
[MAX_PATH
], fn
[MAX_PATH
];
414 if(!GetTempPathA(MAX_PATH
, path
) || !GetTempFileNameA(path
, "mpc_gz", 0, fn
))
417 gzFile gf
= gzopen(fn
, "wb9");
418 if(!gf
|| gzwrite(gf
, (LPVOID
)(LPCSTR
)body
, body
.GetLength()) != body
.GetLength())
426 FILE* f
= fopen(fn
, "rb");
427 if(!f
) {DeleteFileA(fn
); break;}
429 CHAR
* s
= body
.GetBufferSetLength(ftell(f
));
431 int len
= fread(s
, 1, body
.GetLength(), f
);
432 ASSERT(len
== body
.GetLength());
436 hdr
+= "Content-Encoding: gzip\r\n";
442 "Content-Type: %s\r\n"
443 "Content-Length: %d\r\n",
444 mime
, body
.GetLength());
448 static DWORD WINAPI
KillCGI(LPVOID lParam
)
450 HANDLE hProcess
= (HANDLE
)lParam
;
451 if(WaitForSingleObject(hProcess
, 30000) == WAIT_TIMEOUT
)
452 TerminateProcess(hProcess
, 0);
456 bool CWebServer::CallCGI(CWebClientSocket
* pClient
, CStringA
& hdr
, CStringA
& body
, CStringA
& mime
)
458 CString path
= pClient
->m_path
, redir
= path
;
459 if(!ToLocalPath(path
, redir
)) return false;
460 CString ext
= CPath(path
).GetExtension().MakeLower();
462 dir
.RemoveFileSpec();
465 if(!m_cgi
.Lookup(ext
, cgi
) || !CPath(cgi
).FileExists())
468 HANDLE hProcess
= GetCurrentProcess();
469 HANDLE hChildStdinRd
, hChildStdinWr
, hChildStdinWrDup
= NULL
;
470 HANDLE hChildStdoutRd
, hChildStdoutWr
, hChildStdoutRdDup
= NULL
;
472 SECURITY_ATTRIBUTES saAttr
;
473 ZeroMemory(&saAttr
, sizeof(saAttr
));
474 saAttr
.nLength
= sizeof(saAttr
);
475 saAttr
.bInheritHandle
= TRUE
;
477 if(CreatePipe(&hChildStdoutRd
, &hChildStdoutWr
, &saAttr
, 0))
479 BOOL fSuccess
= DuplicateHandle(hProcess
, hChildStdoutRd
, hProcess
, &hChildStdoutRdDup
, 0, FALSE
, DUPLICATE_SAME_ACCESS
);
480 CloseHandle(hChildStdoutRd
);
483 if(CreatePipe(&hChildStdinRd
, &hChildStdinWr
, &saAttr
, 0))
485 BOOL fSuccess
= DuplicateHandle(hProcess
, hChildStdinWr
, hProcess
, &hChildStdinWrDup
, 0, FALSE
, DUPLICATE_SAME_ACCESS
);
486 CloseHandle(hChildStdinWr
);
489 STARTUPINFO siStartInfo
;
490 ZeroMemory(&siStartInfo
, sizeof(siStartInfo
));
491 siStartInfo
.cb
= sizeof(siStartInfo
);
492 siStartInfo
.hStdError
= hChildStdoutWr
;
493 siStartInfo
.hStdOutput
= hChildStdoutWr
;
494 siStartInfo
.hStdInput
= hChildStdinRd
;
495 siStartInfo
.dwFlags
|= STARTF_USESTDHANDLES
|STARTF_USESHOWWINDOW
;
496 siStartInfo
.wShowWindow
= SW_HIDE
;
498 PROCESS_INFORMATION piProcInfo
;
499 ZeroMemory(&piProcInfo
, sizeof(piProcInfo
));
503 if(LPVOID lpvEnv
= GetEnvironmentStrings())
507 CAtlList
<CString
> env
;
508 for(LPTSTR lpszVariable
= (LPTSTR
)lpvEnv
; *lpszVariable
; lpszVariable
+= _tcslen(lpszVariable
)+1)
509 if(lpszVariable
!= (LPTSTR
)lpvEnv
)
510 env
.AddTail(lpszVariable
);
512 env
.AddTail(_T("GATEWAY_INTERFACE=CGI/1.1"));
513 env
.AddTail(_T("SERVER_SOFTWARE=Media Player Classic/6.4.x.y"));
514 env
.AddTail(_T("SERVER_PROTOCOL=") + pClient
->m_ver
);
515 env
.AddTail(_T("REQUEST_METHOD=") + pClient
->m_cmd
);
516 env
.AddTail(_T("PATH_INFO=") + redir
);
517 env
.AddTail(_T("PATH_TRANSLATED=") + path
);
518 env
.AddTail(_T("SCRIPT_NAME=") + redir
);
519 env
.AddTail(_T("QUERY_STRING=") + pClient
->m_query
);
521 if(pClient
->m_hdrlines
.Lookup(_T("content-type"), str
))
522 env
.AddTail(_T("CONTENT_TYPE=") + str
);
523 if(pClient
->m_hdrlines
.Lookup(_T("content-length"), str
))
524 env
.AddTail(_T("CONTENT_LENGTH=") + str
);
526 POSITION pos
= pClient
->m_hdrlines
.GetStartPosition();
529 CString key
= pClient
->m_hdrlines
.GetKeyAt(pos
);
530 CString value
= pClient
->m_hdrlines
.GetNextValue(pos
);
531 key
.Replace(_T("-"), _T("_"));
533 env
.AddTail(_T("HTTP_") + key
+ _T("=") + value
);
539 if(pClient
->GetPeerName(name
, port
))
541 str
.Format(_T("%d"), port
);
542 env
.AddTail(_T("REMOTE_ADDR=")+name
);
543 env
.AddTail(_T("REMOTE_HOST=")+name
);
544 env
.AddTail(_T("REMOTE_PORT=")+str
);
547 if(pClient
->GetSockName(name
, port
))
549 str
.Format(_T("%d"), port
);
550 env
.AddTail(_T("SERVER_NAME=")+name
);
551 env
.AddTail(_T("SERVER_PORT=")+str
);
554 env
.AddTail(_T("\0"));
556 str
= Implode(env
, '\0');
557 envstr
= CStringA(str
, str
.GetLength());
559 FreeEnvironmentStrings((LPTSTR
)lpvEnv
);
562 TCHAR
* cmdln
= new TCHAR
[32768];
563 _sntprintf(cmdln
, 32768, _T("\"%s\" \"%s\""), cgi
, path
);
565 if(hChildStdinRd
&& hChildStdoutWr
)
567 NULL
, cmdln
, NULL
, NULL
, TRUE
, 0,
568 envstr
.GetLength() ? (LPVOID
)(LPCSTR
)envstr
: NULL
,
569 dir
, &siStartInfo
, &piProcInfo
))
572 CreateThread(NULL
, 0, KillCGI
, (LPVOID
)piProcInfo
.hProcess
, 0, &ThreadId
);
574 static const int BUFFSIZE
= 1024;
575 DWORD dwRead
, dwWritten
= 0;
577 int i
= 0, len
= pClient
->m_data
.GetLength();
578 for(; i
< len
; i
+= dwWritten
)
579 if(!WriteFile(hChildStdinWrDup
, (LPCSTR
)pClient
->m_data
+ i
, min(len
- i
, BUFFSIZE
), &dwWritten
, NULL
))
582 CloseHandle(hChildStdinWrDup
);
583 CloseHandle(hChildStdoutWr
);
588 while(i
== len
&& ReadFile(hChildStdoutRdDup
, buff
.GetBuffer(BUFFSIZE
), BUFFSIZE
, &dwRead
, NULL
) && dwRead
)
590 buff
.ReleaseBufferSetLength(dwRead
);
594 int hdrend
= body
.Find("\r\n\r\n");
597 hdr
= body
.Left(hdrend
+2);
598 body
= body
.Mid(hdrend
+4);
601 CloseHandle(hChildStdinRd
);
602 CloseHandle(hChildStdoutRdDup
);
604 CloseHandle(piProcInfo
.hProcess
);
605 CloseHandle(piProcInfo
.hThread
);
609 body
= _T("CGI Error");