📄 httpsrvr.cxx
字号:
PMultipartFormInfo * info = NULL;
// read form parts
while (data.good() && (line.Right(2) != "--")) {
info = new PMultipartFormInfo;
// read MIME information
info->mime.ReadFrom(data);
// get the content type
PString type = info->mime(PHTTP::ContentTypeTag);
// check the encoding
PString encoding = info->mime("Content-Transfer-Encoding");
// accumulate text until another seperator or end of data
PString & buf = info->body;
PINDEX len = 0;
buf.SetSize(len+1);
buf[0] = '\0';
PINDEX sepLen = sep.GetLength();
const char * sepPtr = (const char *)sep;
while (data.good()) {
buf.SetSize(len);
data >> buf[len++];
if ((len >= sepLen) && (memcmp(((const char *)buf) + len - sepLen, sepPtr, sepLen) == 0)) {
char ch;
data >> ch;
if (ch != 0x0d)
data.putback(ch);
else {
data >> ch;
if (ch != 0x0a)
data.putback(ch);
}
len -= sepLen;
break;
}
}
buf.SetSize(len+1);
buf[len] = '\0';
/*
while (data.good()) {
data >> line;
if (line.Find(sep) == 0)
break;
info->body += line + "\n";
}
*/
multipartFormInfoArray.Append(info);
info = NULL;
}
#endif
}
BOOL PHTTPServer::ProcessCommand()
{
PString args;
PINDEX cmd;
// if this is not the first command received by this socket, then set
// the read timeout appropriately.
if (transactionCount > 0)
SetReadTimeout(nextTimeout);
// this will only return false upon timeout or completely invalid command
if (!ReadCommand(cmd, args))
return FALSE;
connectInfo.commandCode = (Commands)cmd;
if (cmd < NumCommands)
connectInfo.commandName = commandNames[cmd];
else {
PINDEX spacePos = args.Find(' ');
connectInfo.commandName = args.Left(spacePos);
args = args.Mid(spacePos);
}
// if no tokens, error
if (args.IsEmpty()) {
OnError(BadRequest, args, connectInfo);
return FALSE;
}
if (!connectInfo.Initialise(*this, args))
return FALSE;
// now that we've decided we did receive a HTTP request, increment the
// count of transactions
transactionCount++;
nextTimeout = connectInfo.GetPersistenceTimeout();
PIPSocket * socket = GetSocket();
WORD myPort = (WORD)(socket != NULL ? socket->GetPort() : 80);
// the URL that comes with Connect requests is not quite kosher, so
// mangle it into a proper URL and do NOT close the connection.
// for all other commands, close the read connection if not persistant
if (cmd == CONNECT)
connectInfo.url = "https://" + args;
else {
connectInfo.url = args;
if (connectInfo.url.GetPort() == 0)
connectInfo.url.SetPort(myPort);
}
BOOL persist;
// make sure the form info is reset for each new operation
connectInfo.ResetMultipartFormInfo();
// If the incoming URL is of a proxy type then call OnProxy() which will
// probably just go OnError(). Even if a full URL is provided in the
// command we should check to see if it is a local server request and process
// it anyway even though we are not a proxy. The usage of GetHostName()
// below are to catch every way of specifying the host (name, alias, any of
// several IP numbers etc).
const PURL & url = connectInfo.GetURL();
if (url.GetScheme() != "http" ||
(url.GetPort() != 0 && url.GetPort() != myPort) ||
(!url.GetHostName() && !PIPSocket::IsLocalHost(url.GetHostName())))
persist = OnProxy(connectInfo);
else {
connectInfo.entityBody = ReadEntityBody();
// Handle the local request
PStringToString postData;
switch (cmd) {
case GET :
persist = OnGET(url, connectInfo.GetMIME(), connectInfo);
break;
case HEAD :
persist = OnHEAD(url, connectInfo.GetMIME(), connectInfo);
break;
case POST :
{
// check for multi-part form POSTs
PString postType = (connectInfo.GetMIME())(ContentTypeTag);
if (postType.Find("multipart/form-data") == 0)
connectInfo.DecodeMultipartFormInfo(postType, connectInfo.entityBody);
else // if (postType *= "x-www-form-urlencoded)
PURL::SplitQueryVars(connectInfo.entityBody, postData);
}
persist = OnPOST(url, connectInfo.GetMIME(), postData, connectInfo);
break;
case P_MAX_INDEX:
default:
persist = OnUnknown(args, connectInfo);
}
}
flush();
// if the function just indicated that the connection is to persist,
// and so did the client, then return TRUE. Note that all of the OnXXXX
// routines above must make sure that their return value is FALSE if
// if there was no ContentLength field in the response. This ensures that
// we always close the socket so the client will get the correct end of file
if (persist && connectInfo.IsPersistant()) {
unsigned max = connectInfo.GetPersistenceMaximumTransations();
if (max == 0 || transactionCount < max)
return TRUE;
}
PTRACE(5, "HTTPServer\tConnection end: " << connectInfo.IsPersistant());
// close the output stream now and return FALSE
Shutdown(ShutdownWrite);
return FALSE;
}
PString PHTTPServer::ReadEntityBody()
{
if (connectInfo.GetMajorVersion() < 1)
return PString();
PString entityBody;
long contentLength = connectInfo.GetEntityBodyLength();
// a content length of > 0 means read explicit length
// a content length of < 0 means read until EOF
// a content length of 0 means read nothing
int count = 0;
if (contentLength > 0) {
entityBody = ReadString((PINDEX)contentLength);
} else if (contentLength == -2) {
ReadLine(entityBody, FALSE);
} else if (contentLength < 0) {
while (Read(entityBody.GetPointer(count+1000)+count, 1000))
count += GetLastReadCount();
entityBody.SetSize(count+1);
}
// close the connection, if not persistant
if (!connectInfo.IsPersistant()) {
PIPSocket * socket = GetSocket();
if (socket != NULL)
socket->Shutdown(PIPSocket::ShutdownRead);
}
return entityBody;
}
PString PHTTPServer::GetServerName() const
{
return "PWLib-HTTP-Server/1.0 PWLib/1.0";
}
void PHTTPServer::SetURLSpace(const PHTTPSpace & space)
{
urlSpace = space;
}
BOOL PHTTPServer::OnGET(const PURL & url,
const PMIMEInfo & info,
const PHTTPConnectionInfo & connectInfo)
{
urlSpace.StartRead();
PHTTPResource * resource = urlSpace.FindResource(url);
if (resource == NULL) {
urlSpace.EndRead();
return OnError(NotFound, url.AsString(), connectInfo);
}
BOOL retval = resource->OnGET(*this, url, info, connectInfo);
urlSpace.EndRead();
return retval;
}
BOOL PHTTPServer::OnHEAD(const PURL & url,
const PMIMEInfo & info,
const PHTTPConnectionInfo & connectInfo)
{
urlSpace.StartRead();
PHTTPResource * resource = urlSpace.FindResource(url);
if (resource == NULL) {
urlSpace.EndRead();
return OnError(NotFound, url.AsString(), connectInfo);
}
BOOL retval = resource->OnHEAD(*this, url, info, connectInfo);
urlSpace.EndRead();
return retval;
}
BOOL PHTTPServer::OnPOST(const PURL & url,
const PMIMEInfo & info,
const PStringToString & data,
const PHTTPConnectionInfo & connectInfo)
{
urlSpace.StartRead();
PHTTPResource * resource = urlSpace.FindResource(url);
if (resource == NULL) {
urlSpace.EndRead();
return OnError(NotFound, url.AsString(), connectInfo);
}
BOOL retval = resource->OnPOST(*this, url, info, data, connectInfo);
urlSpace.EndRead();
return retval;
}
BOOL PHTTPServer::OnProxy(const PHTTPConnectionInfo & connectInfo)
{
return OnError(BadGateway, "Proxy not implemented.", connectInfo) &&
connectInfo.GetCommandCode() != CONNECT;
}
struct httpStatusCodeStruct {
const char * text;
int code;
BOOL allowedBody;
int majorVersion;
int minorVersion;
};
static const httpStatusCodeStruct * GetStatusCodeStruct(int code)
{
static const httpStatusCodeStruct httpStatusDefn[] = {
// First entry MUST be InternalServerError
{ "Internal Server Error", PHTTP::InternalServerError, 1 },
{ "OK", PHTTP::RequestOK, 1 },
{ "Unauthorised", PHTTP::UnAuthorised, 1 },
{ "Forbidden", PHTTP::Forbidden, 1 },
{ "Not Found", PHTTP::NotFound, 1 },
{ "Not Modified", PHTTP::NotModified },
{ "No Content", PHTTP::NoContent },
{ "Bad Gateway", PHTTP::BadGateway, 1 },
{ "Bad Request", PHTTP::BadRequest, 1 },
{ "Continue", PHTTP::Continue, 1, 1, 1 },
{ "Switching Protocols", PHTTP::SwitchingProtocols, 1, 1, 1 },
{ "Created", PHTTP::Created, 1 },
{ "Accepted", PHTTP::Accepted, 1 },
{ "Non-Authoritative Information", PHTTP::NonAuthoritativeInformation, 1, 1, 1 },
{ "Reset Content", PHTTP::ResetContent, 0, 1, 1 },
{ "Partial Content", PHTTP::PartialContent, 1, 1, 1 },
{ "Multiple Choices", PHTTP::MultipleChoices, 1, 1, 1 },
{ "Moved Permanently", PHTTP::MovedPermanently, 1 },
{ "Moved Temporarily", PHTTP::MovedTemporarily, 1 },
{ "See Other", PHTTP::SeeOther, 1, 1, 1 },
{ "Use Proxy", PHTTP::UseProxy, 1, 1, 1 },
{ "Payment Required", PHTTP::PaymentRequired, 1, 1, 1 },
{ "Method Not Allowed", PHTTP::MethodNotAllowed, 1, 1, 1 },
{ "None Acceptable", PHTTP::NoneAcceptable, 1, 1, 1 },
{ "Proxy Authetication Required", PHTTP::ProxyAuthenticationRequired, 1, 1, 1 },
{ "Request Timeout", PHTTP::RequestTimeout, 1, 1, 1 },
{ "Conflict", PHTTP::Conflict, 1, 1, 1 },
{ "Gone", PHTTP::Gone, 1, 1, 1 },
{ "Length Required", PHTTP::LengthRequired, 1, 1, 1 },
{ "Unless True", PHTTP::UnlessTrue, 1, 1, 1 },
{ "Not Implemented", PHTTP::NotImplemented, 1 },
{ "Service Unavailable", PHTTP::ServiceUnavailable, 1, 1, 1 },
{ "Gateway Timeout", PHTTP::GatewayTimeout, 1, 1, 1 }
};
// make sure the error code is valid
for (PINDEX i = 0; i < PARRAYSIZE(httpStatusDefn); i++)
if (code == httpStatusDefn[i].code)
return &httpStatusDefn[i];
return httpStatusDefn;
}
BOOL PHTTPServer::StartResponse(StatusCode code,
PMIMEInfo & headers,
long bodySize)
{
if (connectInfo.majorVersion < 1)
return FALSE;
httpStatusCodeStruct dummyInfo;
const httpStatusCodeStruct * statusInfo;
if (connectInfo.commandCode < NumCommands)
statusInfo = GetStatusCodeStruct(code);
else {
dummyInfo.text = "";
dummyInfo.code = code;
dummyInfo.allowedBody = TRUE;
dummyInfo.majorVersion = connectInfo.majorVersion;
dummyInfo.minorVersion = connectInfo.minorVersion;
statusInfo = &dummyInfo;
}
// output the command line
*this << "HTTP/" << connectInfo.majorVersion << '.' << connectInfo.minorVersion
<< ' ' << statusInfo->code << ' ' << statusInfo->text << "\r\n";
BOOL chunked = FALSE;
// If do not have user set content length, decide if we should add one
if (!headers.Contains(ContentLengthTag)) {
if (connectInfo.minorVersion < 1) {
// v1.0 client, don't put in ContentLength if the bodySize is zero because
// that can be confused by some browsers as meaning there is no body length.
if (bodySize > 0)
headers.SetAt(ContentLengthTag, bodySize);
}
else {
// v1.1 or later, see if will use chunked output
chunked = bodySize == P_MAX_INDEX;
if (chunked)
headers.SetAt(TransferEncodingTag, ChunkedTag);
else if (bodySize >= 0 && bodySize < P_MAX_INDEX)
headers.SetAt(ContentLengthTag, bodySize);
}
}
*this << setfill('\r') << headers;
#ifdef STRANGE_NETSCAPE_BUG
// The following is a work around for a strange bug in Netscape where it
// locks up when a persistent connection is made and data less than 1k
// (including MIME headers) is sent. Go figure....
if (bodySize < 1024 && connectInfo.GetMIME()(UserAgentTag).Find("Mozilla/2.0") != P_MAX_INDEX)
nextTimeout.SetInterval(STRANGE_NETSCAPE_BUG*1000);
#endif
return chunked;
}
void PHTTPServer::SetDefaultMIMEInfo(PMIMEInfo & info,
const PHTTPConnectionInfo & connectInfo)
{
PTime now;
if (!info.Contains(DateTag))
info.SetAt(DateTag, now.AsString(PTime::RFC1123, PTime::GMT));
if (!info.Contains(MIMEVersionTag))
info.SetAt(MIMEVersionTag, "1.0");
if (!info.Contains(ServerTag))
info.SetAt(ServerTag, GetServerName());
if (connectInfo.IsPersistant()) {
if (connectInfo.IsProxyConnection()) {
PTRACE(5, "HTTPServer\tSetting proxy persistant response");
info.SetAt(ProxyConnectionTag, KeepAliveTag);
}
else {
PTRACE(5, "HTTPServer\tSetting direct persistant response");
info.SetAt(ConnectionTag, KeepAliveTag);
}
}
}
BOOL PHTTPServer::OnUnknown(const PCaselessString & cmd,
const PHTTPConnectionInfo & connectInfo)
{
return OnError(NotImplemented, cmd, connectInfo);
}
BOOL PHTTPServer::OnError(StatusCode code,
const PCaselessString & extra,
const PHTTPConnectionInfo & connectInfo)
{
const httpStatusCodeStruct * statusInfo = GetStatusCodeStruct(code);
if (!connectInfo.IsCompatible(statusInfo->majorVersion, statusInfo->minorVersion))
statusInfo = GetStatusCodeStruct((code/100)*100);
PMIMEInfo headers;
SetDefaultMIMEInfo(headers, connectInfo);
if (!statusInfo->allowedBody) {
StartResponse(code, headers, 0);
return statusInfo->code == RequestOK;
}
PString reply;
if (extra.Find("<body") != P_MAX_INDEX)
reply = extra;
else {
PHTML html;
html << PHTML::Title()
<< statusInfo->code
<< ' '
<< statusInfo->text
<< PHTML::Body()
<< PHTML::Heading(1)
<< statusInfo->code
<< ' '
<< statusInfo->text
<< PHTML::Heading(1)
<< extra
<< PHTML::Body();
reply = html;
}
headers.SetAt(ContentTypeTag, "text/html");
StartResponse(code, headers, reply.GetLength());
WriteString(reply);
return statusInfo->code == RequestOK;
}
//////////////////////////////////////////////////////////////////////////////
// PHTTPSimpleAuth
void PHTTPAuthority::DecodeBasicAuthority(const PString & authInfo,
PString & username,
PString & password)
{
PString decoded;
if (authInfo(0, 5) *= "Basic ")
decoded = PBase64::Decode(authInfo(6, P_MAX_INDEX));
else
decoded = PBase64::Decode(authInfo);
PINDEX colonPos = decoded.Find(':');
if (colonPos == P_MAX_INDEX) {
username = decoded;
password = PString();
}
else {
username = decoded.Left(colonPos).Trim();
password = decoded.Mid(colonPos+1).Trim();
}
}
BOOL PHTTPAuthority::IsActive() const
{
return TRUE;
}
//////////////////////////////////////////////////////////////////////////////
// PHTTPSimpleAuth
PHTTPSimpleAuth::PHTTPSimpleAuth(const PString & realm_,
const PString & username_,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -