/*****************************************************************************/ /* body.c Request body transfer from client, any content processing, etc. The functions BodyProcessUrlEncoded() and BodyProcessMultipartFormData() process request body data once it has been read from the client. When data should be passed on to the calling/using function is calls the AST with the following storage set. Note that the 'rqBody.Data..' buffer can contain either raw or processed data, but is always what the current function requires to continue processing the request body. 'rqptr->rqBody.DataStatus' containing the VMS status relevant to the processing 'rqptr->rqBody.DataPtr' pointing to the start of virtual block(s) of data 'rqptr->rqBody.DataCount' containing the number of bytes in the buffer (whole blocks or left-over) 'rqptr->rqBody.DataVBN' the virtual block number (1 to whatever, if applicable) This sort of processing using ASTs is particularly painful. Lots of buffering is required to allow for octets that must be processed together (e.g. %xx URL-encoded) to be delivered split between two reads. To allow file-system functions to perform block I/O the AST is only ever delivered with a buffer containing a whole number of 512 bytes blocks unless it is the final call when it may contain from 1 to 511 bytes. Transfer-Encoding: chunked -------------------------- Chunked transfer encoding is a HTTP/1.1 fact of life. As the binary content of these bodies is always smaller after decoding they are unchunked in the same buffer space that is used to read the chunked stream from the network. Testing chunked upload can be performed with the WASD Bench utility (v1.2 and later). With a suitable POST target in the file-system merely upload files of various sizes and contents into that file-system area and compare the upload with the original using the DIFFERENCE utility. $ WB /POST= /TRANSFER=CHUNKED "http://host/test/" $ DIFFERENCE TEST:[000000] $ WB /POST= /TRANSFER=CHUNKED=300 "http://host/test/" $ WB /POST= /TRANSFER=CHUNKED=0 "http://host/test/" Content-Encoding: gzip ---------------------- GZIP compression for request bodies is available in conjunction with the GZIP.C module and ZLIB library (sharable image). As the decompressed content is almost invariably larger than the original raw data some form of double buffering is required. This keeps the network data intact while having the ZLIB inflation function produce usually multiple, successive output buffers that are given to the body processing function(s) independently. Using a similar approach as with chunked upload testing GZIP compressed upload can be performed with the WASD Bench utility (v1.2 and later). It is first necessary to create a GZIPed object using the GZIP utility. This behaves as if it a client-generated GZIP request body. $ COPY $ GZIP $ WB /POST=-gz /ENCODING="gzip" "http://host/test/" $ DIFFERENCE TEST:[000000] Content-Type: application/x-www-form-urlencoded ----------------------------------------------- When the processing function BodyProcessUrlEncoded() is used ... If the document 'Content-Type:' is "application/x-www-form-urlencoded" (i.e. generated from an HTML form) all field names and delimiting symbols are eliminated and the field(s) content (only) converted into plain text. Hence a text document can be POSTed using an HTML form with only the content ending up in the file. A field name (beginning with) "hexencoded" is expected to contain hexadecimal encoded bytes (i.e. two hexadecimal digits per byte), as well as some url-encoded characters (e.g. newlines, 0x0a). The contents of this field are decoded before inclusion in the file. This was done to allow non-text files to be copied around. Content-Type: multipart/form-data --------------------------------- When the processing function BodyProcessMultipartFormData() is used ... This module can process a request body according to RFC-1867, "Form-based File Upload in HTML". As yet it is not a full implementation. It will not process "multipart/mixed" subsections. The 'Content-Type:' is "multipart/form-data". The implementation herein is very basic, a facility only to allow uploads of files into the server administered file system. The action= tag of the form must specify the directory (URL format) in which the uploaded file will be created. Multipart/form-data Fields -------------------------- All file names supplied within a multipart/form-data request are relative to the path information supplied in the header request. Hence it must supply a directory specification. It is an error condition if this directory is not specified, and all file specifications are generated by appending file names to this directory. In addition to file uploads, specific field names within a multipart/form-data request are detected and used as additional information or to initiate actions. These field names are case insensistive, as is any content within them. When uploading files, the field "UploadFileName" contents, when received before the file contents themselves (i.e. "text" field occurs in the form before the "file" field) provides an alternative name to the original file. Any other value present in the action field is reported as an error. All required file names must be specified "file.type" prior to the action being initiated (i.e. name fields must be present in the form before the action field). The following (hopefully self-documenting) field names can be used to pass file names for action: o "FileName" o "UploadFileName" o "Protection" o "PreviewOnly" o "PreviewNote" o "Success" o "Hidden$LF" Any other field names present in the request are reported as errors. VERSION HISTORY --------------- 22-NOV-2020 MGD content length now 64 bit 21-MAR-2017 MGD bugfix; use rqHeader.RequestBody.. for body with header 05-SEP-2015 MGD accomodate HTTP/2 17-JUN-2010 MGD significant rework to function()alise common code improve performance with multiblock of 127 (per JPP) 30-APR-2010 MGD BODY.C make MultipartContentType(Ptr) a dynamic structure as Microsoft endeavour to include application data along with MIME content-type, see ... http://msdn.microsoft.com/en-us/library/aa338205.aspx and an example (no kidding!) ... "application/vnd.ms.powerpoint.template.macroEnabled.12application/x-font" 27-OCT-2009 MGD bugfix; BodyReadUnEncode() unchunk all but scripts 07-JUN-2007 MGD BodyProcessReadAll() set buffer size to content length 17-JAN-2007 MGD bugfix; BodyReadBegin() 413 set status before declaring AST 14-NOV-2004 MGD bugfix; BodyRead() maximum exceeded must set read error status before calling AST (jpp@esme.fr) 17-OCT-2004 MGD inflate gzip content-encoding, by default script body transfers are now un-encoded 20-JUL-2004 MGD HTTP/1.1 compliance, provide "100 Continue" response before reading body, decode "Transfer-encoding: chunked" request bodies 15-AUG-2003 MGD where CDATA constraints make using entity impossible use a field name of hidden$lf and ^ substituted for it 21-JUN-2003 MGD bugfix; data to be read needs to be the smaller of remaining body or buffer size (jpp@esme.fr) 24-MAR-2003 MGD bugfix; processing of DCL module script processing restart 05-DEC-2002 MGD allow for white-space in multipart file names 25-AUG-2002 MGD introduce fab$b_rfm and fab$b_rat as fields to allow PUT.C to specifically set these attributes as required, bugfix; restart MIME boundary matching algorithm using the current character (to allow for ) 01-AUG-2002 MGD bugfix; when discarding via BodyReadBegin() use BodyRead() to queue a network read only if data is outstanding 30-JUN-2002 MGD allow BodyReadBegin() to reset ASTs for RequestBodyDiscard() 07-JUN-2002 MGD bugfix; sanity check where there shouldn't be one 02-FEB-2002 MGD couldn't avoid doing something about it any longer */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif #include #include #include "wasd.h" #define WASD_MODULE "BODY" /******************/ /* global storage */ /******************/ int BodyMultiBlockCount = PUT_MULTIBLOCK_COUNT_DEFAULT; /********************/ /* external storage */ /********************/ extern BOOL GzipAccept; extern int NetReadBufferSize; extern int ToLowerCase[], ToUpperCase[]; extern char ErrorSanityCheck[]; extern CONFIG_STRUCT Config; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Initialize and initiate the first request body read from the client. If request body data arrived with the request header a pseudo-read occurs, with explicit AST delivery. Otherwise just read from the network. */ void BodyReadBegin ( REQUEST_STRUCT *rqptr, REQUEST_AST AstFunction, REQUEST_AST ProcessFunction ) { int status, DataSize, MaxKbytes; int64 ContentLength64; char *cptr; char String [256]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyReadBegin() chunked:!&B gzip:!&B length:!@SQ expected:!@SQ \ ast:!&A process:!&A", rqptr->rqHeader.TransferEncodingChunked, rqptr->rqHeader.ContentEncodingGzip, &rqptr->rqHeader.ContentLength64, rqptr->rqHeader.XExpectedEntityLength64, AstFunction, ProcessFunction); if (rqptr->rqBody.AstFunction) { if (AstFunction == (REQUEST_AST)&DclHttpInput && rqptr->rqBody.AstFunction == (REQUEST_AST)&DclHttpInput) { /************************************************/ /* special case - restarting DCL script process */ /************************************************/ /* provide the same data that was previously supplied */ SysDclAst (&DclHttpInput, rqptr); return; } /* this shouldn't happen */ ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_BUGCHECK; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } if (!(ContentLength64 = rqptr->rqHeader.ContentLength64)) /* a WebDAVFS/3.0.0 and/or OS X (Apple) quirk */ ContentLength64 = rqptr->rqHeader.XExpectedEntityLength64; if (ContentLength64) { /* before the "100 Continue" - request body allowed to be this big? */ if (!(MaxKbytes = rqptr->rqPathSet.PutMaxKbytes)) MaxKbytes = Config.cfMisc.PutMaxKbytes; if (!rqptr->ProxyTaskPtr && ContentLength64 >> 10 > MaxKbytes) { cptr = MsgFor(rqptr,MSG_REQUEST_BODY_MAX); status = FaoToBuffer (String, sizeof(String), NULL, cptr, rqptr->rqHeader.MethodName, ContentLength64 >> 10, MaxKbytes); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->PersistentRequest = rqptr->PersistentResponse = false; rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ABORT; rqptr->rqResponse.HttpStatus = 413; ErrorGeneral (rqptr, String, FI_LI); /* RequestBodyDiscard() uses this for progress determination */ SysDclAst (rqptr->rqBody.AstFunction = AstFunction, rqptr); return; } } rqptr->rqBody.AstFunction = AstFunction; rqptr->rqBody.ProcessFunction = ProcessFunction; rqptr->rqBody.DataCount = rqptr->rqBody.ContentCount64 = 0; rqptr->rqBody.ContentLength64 = ContentLength64; rqptr->rqBody.DataVBN = 1; if (!ContentLength64 && !rqptr->rqHeader.TransferEncodingChunked) { /* no body to be supplied */ rqptr->rqBody.DataPtr = ""; rqptr->rqBody.DataSize = 0; rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_NONE; rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ENDOFFILE; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } if (rqptr->Http2Stream.Http2Ptr) { /* HTTP/2 allocate now that it is needed */ rqptr->rqNet.ReadBufferSize = rqptr->Http2Stream.Http2Ptr->ServerMaxFrameSize; rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, rqptr->rqNet.ReadBufferSize); } else { /* HTTP/1.n allow for any content received along with the header */ rqptr->NetIoPtr->ReadCount = rqptr->BytesRx64 - rqptr->rqHeader.RequestHeaderLength; } rqptr->rqBody.DataPtr = rqptr->rqNet.ReadBufferPtr; rqptr->rqBody.DataSize = rqptr->rqNet.ReadBufferSize; if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_1) { rqptr->rqBody.UnEncodeStream = BodyReadUnEncode (rqptr); if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "Unencode stream: !&B", rqptr->rqBody.UnEncodeStream); /* is a "100 Continue" interim response required at this point? */ if ((rqptr->rqHeader.Expect100Continue || rqptr->rqHeader.Method == HTTP_METHOD_PUT || rqptr->rqHeader.Method == HTTP_METHOD_POST) && !rqptr->rqResponse.HeaderSent && !rqptr->DclTaskPtr && !rqptr->DECnetTaskPtr && !rqptr->ProxyTaskPtr) ResponseHeader100Continue (rqptr); } /* once we start reading into the buffer the request header is kaput */ rqptr->rqHeader.RequestHeaderPtrInvalid = true; /* may be reset by using ..UrlEncoded() or ..MultipartFormData() */ if (ConfigSameContentType (rqptr->rqHeader.ContentTypePtr, "text/", 5)) rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_TEXT; else rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_OTHER; /* ensure this does not exceed any specified content length */ if (rqptr->rqBody.ContentLength64 && rqptr->NetIoPtr->ReadCount > rqptr->rqBody.ContentLength64) rqptr->NetIoPtr->ReadCount = rqptr->rqBody.ContentLength64; if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "Length:!@SQ BytesRx:!@SQ HeaderLength:!UL (!UL) IOsb.Count:!UL", &rqptr->rqBody.ContentLength64, &rqptr->BytesRx64, rqptr->rqHeader.RequestHeaderLength, rqptr->BytesRx64 - rqptr->rqHeader.RequestHeaderLength, rqptr->NetIoPtr->ReadCount); if (rqptr->rqHeader.RequestBodyCount) { /* data arrived with the request header, provide that */ rqptr->rqBody.DataPtr = rqptr->rqHeader.RequestBodyPtr; rqptr->NetIoPtr->ReadCount = rqptr->rqHeader.RequestBodyCount; rqptr->NetIoPtr->ReadStatus = SS$_NORMAL; SysDclAst (&BodyReadAst, rqptr); rqptr->rqHeader.RequestBodyPtr = NULL; rqptr->rqHeader.RequestBodyCount = 0; return; } if (rqptr->rqHeader.TransferEncodingChunked) { /* don't know at this stage, so just read as much as possible */ DataSize = rqptr->rqNet.ReadBufferSize; } else { /* data to be read is the smaller of remaining body or buffer size */ DataSize = rqptr->rqBody.ContentLength64 - rqptr->rqBody.ContentCount64; if (DataSize > rqptr->rqBody.DataSize) DataSize = rqptr->rqBody.DataSize; } NetRead (rqptr, &BodyReadAst, rqptr->rqBody.DataPtr, DataSize); } /*****************************************************************************/ /* Should the request body be unencoded or be delivered as raw stream? Return true or false. */ BOOL BodyReadUnEncode (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyReadUnEncode()"); if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_1) { if (rqptr->ProxyTaskPtr) return (false); else if (rqptr->rqHeader.TransferEncodingChunked) { if (rqptr->ScriptName[0] && !rqptr->rqPathSet.ScriptBodyDecode) return (false); /* all other chunked */ return (true); } else if (rqptr->rqHeader.ContentEncodingGzip) { if (GzipAccept && rqptr->rqPathSet.ScriptBodyDecode) return (true); else return (false); } else return (false); } return (false); } /*****************************************************************************/ /* If there is still more to be read of the request body (as indicated by a difference between what has been read and the content-length indicated in the request header) the queue another read. If the entire request body has been read generate an end-of-file status. */ void BodyRead (REQUEST_STRUCT *rqptr) { int status, DataSize, MaxKbytes; char *cptr; char String [256]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyRead() !&B !UL !@SQ/!@SQ", rqptr->rqBody.UnEncodeStream, rqptr->rqBody.ChunkState, &rqptr->rqBody.ContentCount64, &rqptr->rqBody.ContentLength64); if (rqptr->RequestState == REQUEST_STATE_ABORT) { rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ABORT; rqptr->rqBody.DataCount = rqptr->NetIoPtr->ReadCount = 0; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } /* ongoing check - request body allowed to be this big? */ if (!(MaxKbytes = rqptr->rqPathSet.PutMaxKbytes)) MaxKbytes = Config.cfMisc.PutMaxKbytes; if (!rqptr->DclTaskPtr && !rqptr->ProxyTaskPtr && (rqptr->rqBody.ContentCount64 >> 10) > MaxKbytes) { cptr = MsgFor(rqptr,MSG_REQUEST_BODY_MAX); status = FaoToBuffer (String, sizeof(String), NULL, cptr, rqptr->rqHeader.MethodName, rqptr->rqBody.ContentCount64 >> 10, MaxKbytes); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->rqResponse.HttpStatus = 413; ErrorGeneral (rqptr, String, FI_LI); rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ABORT; rqptr->rqBody.DataCount = rqptr->NetIoPtr->ReadCount = 0; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } if (rqptr->rqBody.UnEncodeStream && rqptr->rqHeader.ContentEncodingGzip && (rqptr->NetIoPtr->ReadCount || rqptr->GzipCompress.InflateAvailIn)) { /* there is still data that can be fed into the inflate function */ BodyReadProcess (rqptr); return; } if (rqptr->rqHeader.TransferEncodingChunked) { /********************/ /* transfer chunked */ /********************/ if (rqptr->rqBody.UnEncodeStream) { /* being unencoded (therefore we can detect the end of it) */ if (rqptr->rqBody.ChunkState != CHUNK_STATE_END) { /* not the end of the chunked body just yet! */ rqptr->rqBody.DataPtr = rqptr->rqNet.ReadBufferPtr; rqptr->rqBody.DataSize = rqptr->rqNet.ReadBufferSize; NetRead (rqptr, &BodyReadAst, rqptr->rqBody.DataPtr, rqptr->rqBody.DataSize); return; } /* fall through as end-of-body */ } else { /* not being un-encoded (keep reading 'till the bitter end) */ rqptr->rqBody.DataPtr = rqptr->rqNet.ReadBufferPtr; rqptr->rqBody.DataSize = rqptr->rqNet.ReadBufferSize; NetRead (rqptr, &BodyReadAst, rqptr->rqBody.DataPtr, rqptr->rqBody.DataSize); return; } } if (!rqptr->rqHeader.TransferEncodingChunked && rqptr->rqBody.ContentCount64 < rqptr->rqBody.ContentLength64) { /*************/ /* read more */ /*************/ rqptr->rqBody.DataPtr = rqptr->rqNet.ReadBufferPtr; rqptr->rqBody.DataSize = rqptr->rqNet.ReadBufferSize; /* data to be read is the smaller of remaining body or buffer size */ DataSize = rqptr->rqBody.ContentLength64 - rqptr->rqBody.ContentCount64; if (DataSize > rqptr->rqBody.DataSize) DataSize = rqptr->rqBody.DataSize; NetRead (rqptr, &BodyReadAst, rqptr->rqBody.DataPtr, DataSize); return; } /***************/ /* end of body */ /***************/ rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ENDOFFILE; rqptr->rqBody.DataCount = rqptr->NetIoPtr->ReadCount = 0; if (rqptr->rqHeader.ContentLength64) { if (rqptr->rqHeader.ContentLength64 != rqptr->rqBody.ContentCount64) { if (WATCHING (rqptr, WATCH_REQUEST_BODY)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "Content-Length: !@SQ !!= !@SQ", rqptr->rqHeader.ContentLength64, rqptr->rqBody.ContentCount64); rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ABORT; } } else if (rqptr->rqHeader.XExpectedEntityLength64) { /* an OSX (Apple) WebDAV quirk */ if (rqptr->rqHeader.XExpectedEntityLength64 != rqptr->rqBody.ContentCount64) { if (WATCHING (rqptr, WATCH_REQUEST_BODY)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "X-Expected-Entity-Length: !@SQ !!= !@SQ", rqptr->rqHeader.XExpectedEntityLength64, rqptr->rqBody.ContentCount64); rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus = SS$_ABORT; } } if (rqptr->rqBody.ProcessFunction) SysDclAst (rqptr->rqBody.ProcessFunction, rqptr); else SysDclAst (rqptr->rqBody.AstFunction, rqptr); } /*****************************************************************************/ /* Network read has completed. This function will never be called for ENDOFFILE, only success or legitimate error status. This function accepts data specified by the IO status block (NetIoPtr->ReadStatus and NetIoPtr->ReadCount). */ void BodyReadAst (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyReadAst() !&F !&S !UL !@SQ !@SQ", &BodyReadAst, rqptr->NetIoPtr->ReadStatus, rqptr->NetIoPtr->ReadCount, &rqptr->rqBody.ContentLength64, &rqptr->rqBody.ContentCount64); if (VMSnok (rqptr->NetIoPtr->ReadStatus)) { /* error */ rqptr->rqBody.DataCount = 0; rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus; if (!rqptr->rqResponse.HttpStatus) { rqptr->rqResponse.HttpStatus = 400; rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ); ErrorVmsStatus (rqptr, rqptr->NetIoPtr->ReadStatus, FI_LI); } SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } if (rqptr->rqBody.UnEncodeStream && rqptr->rqHeader.TransferEncodingChunked) { /********************/ /* transfer chunked */ /********************/ BodyTransferChunked (rqptr); if (VMSok (rqptr->NetIoPtr->ReadStatus)) { if (!rqptr->NetIoPtr->ReadCount) { /* need to read more of the body from the client */ BodyRead (rqptr); return; } /* otherwise drop thru to further process */ } else { /* an error was reported */ SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } } else { /* keep track of how much raw data has been read of the body */ rqptr->rqBody.ContentCount64 += rqptr->NetIoPtr->ReadCount; } BodyReadProcess (rqptr); } /*****************************************************************************/ /* This function called after a real network read has completed or after a GZIP encoded body chunk has been directly identified in BodyRead() (bypassing BodyReadAst()). This function accepts data specified by the IO status block (NetIoPtr->ReadStatus and NetIoPtr->ReadCount) and provides data identified by the body data structure (rqBody.DataStatus and rqptr->rqBody.DataCount). */ void BodyReadProcess (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyReadProcess() !&F !&S !UL !@SQ !@SQ", &BodyReadProcess, rqptr->NetIoPtr->ReadStatus, rqptr->NetIoPtr->ReadCount, rqptr->rqBody.ContentLength64, rqptr->rqBody.ContentCount64); if (rqptr->rqBody.UnEncodeStream && rqptr->rqHeader.ContentEncodingGzip) { /******************/ /* content GZIPed */ /******************/ BodyContentGzip (rqptr); if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyContentGzip() !&S !UL", rqptr->rqBody.DataStatus, rqptr->rqBody.DataCount); if (VMSok (rqptr->rqBody.DataStatus)) { if (!rqptr->rqBody.DataCount) { /* need to provide more of the body from the client */ BodyRead (rqptr); return; } /* otherwise drop thru to further process */ } else { /* an error was reported */ SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } } else { rqptr->rqBody.DataStatus = rqptr->NetIoPtr->ReadStatus; rqptr->rqBody.DataCount = rqptr->NetIoPtr->ReadCount; } if (WATCHING (rqptr, WATCH_REQUEST_BODY)) { if (rqptr->rqHeader.ContentEncodingGzip) WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "BODY gzip !UL/!@SQ bytes", rqptr->rqBody.DataCount, &rqptr->rqBody.ContentCount64); else if (rqptr->rqBody.ContentLength64) WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "BODY !UL/!@SQ/!@SQ bytes", rqptr->rqBody.DataCount, &rqptr->rqBody.ContentCount64, &rqptr->rqBody.ContentLength64); else WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "BODY !UL/!@SQ bytes", rqptr->rqBody.DataCount, &rqptr->rqBody.ContentCount64); WatchDataDump (rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount); } if (rqptr->rqBody.ProcessFunction) (*rqptr->rqBody.ProcessFunction)(rqptr); else SysDclAst (rqptr->rqBody.AstFunction, rqptr); } /*****************************************************************************/ /* Processes an RFC2068 (HTTP/1.1) chunked body. */ void BodyTransferChunked (REQUEST_STRUCT *rqptr) { int cnt, tbcnt, status, ChunkCount, ChunkSize, ChunkState; char *bptr, *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyTransferChunked() !&B !UL !&X !UL !&S !UL !UL", rqptr->rqBody.UnEncodeStream, rqptr->rqBody.ChunkState, rqptr->rqBody.DataPtr, rqptr->NetIoPtr->ReadCount, rqptr->NetIoPtr->ReadStatus, rqptr->rqBody.ChunkCount, rqptr->rqBody.ChunkSize); if (rqptr->rqBody.ChunkState == CHUNK_STATE_BEGIN) { /**************/ /* initialize */ /**************/ /* initialize the chunk specific storage */ rqptr->rqBody.ChunkCount = rqptr->rqBody.ChunkSize = 0; rqptr->rqBody.ChunkSizeString[0] = '\0'; rqptr->rqBody.ChunkState = CHUNK_STATE_SIZE; } /****************/ /* process data */ /****************/ bptr = cptr = rqptr->rqBody.DataPtr; cnt = rqptr->NetIoPtr->ReadCount; ChunkCount = rqptr->rqBody.ChunkCount; ChunkSize = rqptr->rqBody.ChunkSize; ChunkState = rqptr->rqBody.ChunkState; while (ChunkState != CHUNK_STATE_END && cnt) { if (ChunkState == CHUNK_STATE_SIZE && cnt) { /**********************/ /* getting chunk size */ /**********************/ zptr = rqptr->rqBody.ChunkSizeString + sizeof(rqptr->rqBody.ChunkSizeString)-1; /* step to the start of any partly read hex number */ for (sptr = rqptr->rqBody.ChunkSizeString; *sptr; sptr++); if (!rqptr->rqBody.ChunkSizeString[0] && !isxdigit(*cptr)) { BodyTransferChunkedError (rqptr, FI_LI); return; } while (cnt && isxdigit(*cptr) && sptr < zptr) { /* getting hex number string */ *sptr++ = *cptr++; cnt--; } *sptr = '\0'; if (cnt) { /* some constraints on the potential hex size string */ if ((*cptr != ';' && !ISLWS(*cptr) && NOTEOL(*cptr))) { BodyTransferChunkedError (rqptr, FI_LI); return; } if (!rqptr->rqBody.ChunkSizeString[0]) { BodyTransferChunkedError (rqptr, FI_LI); return; } if (strlen(rqptr->rqBody.ChunkSizeString) > 8) { BodyTransferChunkedError (rqptr, FI_LI); return; } /* line format looks acceptable */ ChunkSize = strtol (rqptr->rqBody.ChunkSizeString, NULL, 16); ChunkState = CHUNK_STATE_EOL; rqptr->rqBody.ContentCount64 += ChunkSize; if (WATCHING (rqptr, WATCH_REQUEST_BODY)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "CHUNK (0x!AZ) !UL bytes", rqptr->rqBody.ChunkSizeString, ChunkSize); } } if (ChunkState == CHUNK_STATE_EOL && cnt) { /***********************/ /* finding end-of-line */ /***********************/ while (cnt && !ISEOL(*cptr)) { cptr++; cnt--; } if (cnt && ISEOL(*cptr)) { /* reached end-of-line, step to the beginning of the data */ if (*cptr == '\r') { cptr++; cnt--; } if (cnt && *cptr == '\n') { cptr++; cnt--; ChunkState = CHUNK_STATE_DATA; } else { BodyTransferChunkedError (rqptr, FI_LI); return; } } if (!ChunkSize) { /*****************/ /* end of chunks */ /*****************/ ChunkState = CHUNK_STATE_TRAILER; if (cnt && ISEOL(*cptr)) { /* trailing terminating empty line follows immediately */ if (*cptr == '\r') { cptr++; cnt--; } if (cnt && *cptr == '\n') { cptr++; cnt--; } ChunkState = CHUNK_STATE_END; if (WATCHING (rqptr, WATCH_REQUEST_BODY)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "CHUNK trailer 0 bytes"); break; } } } if (ChunkState == CHUNK_STATE_DATA && cnt) { /*******************/ /* un-encode chunk */ /*******************/ if (bptr == cptr) { /* start of buffer, no need to move contents */ if (cnt <= ChunkSize - ChunkCount) { /* full buffer if less or equal to that outstanding */ cptr += cnt; bptr += cnt; ChunkCount += cnt; cnt = 0; } else { /* less than full buffer will complete that outstanding */ cptr += ChunkSize - ChunkCount; bptr += ChunkSize - ChunkCount; cnt -= ChunkSize - ChunkCount; ChunkCount = ChunkSize; } } else { /* move the chunk data forward in the buffer */ while (cnt && ChunkCount < ChunkSize) { *bptr++ = *cptr++; ChunkCount++; cnt--; } } if (WATCHING (rqptr, WATCH_REQUEST_BODY)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "CHUNK !UL/!UL bytes", ChunkCount, ChunkSize); if (ChunkCount >= ChunkSize) { /* copied all of this chunk */ ChunkCount = ChunkSize = 0; rqptr->rqBody.ChunkSizeString[0] = '\0'; ChunkState = CHUNK_STATE_EOD; } } if (ChunkState == CHUNK_STATE_EOD && cnt) { /***********************/ /* finding end-of-data */ /***********************/ /* basically just the trailing */ if (*cptr == '\r') { cptr++; cnt--; } if (cnt && *cptr == '\n') { cptr++; cnt--; ChunkState = CHUNK_STATE_SIZE; } else { BodyTransferChunkedError (rqptr, FI_LI); return; } } if (ChunkState == CHUNK_STATE_TRAILER && cnt) { /********************/ /* trailing trailer */ /********************/ ChunkState = BodyTransferChunkedTrailer (rqptr, cptr, cnt); if (!ChunkState) { rqptr->NetIoPtr->ReadStatus = SS$_ENDOFFILE; rqptr->NetIoPtr->ReadCount = 0; return; } } /* adjust to account for any encoding overhead from the buffer */ rqptr->NetIoPtr->ReadCount = bptr - rqptr->rqBody.DataPtr; } rqptr->rqBody.ChunkCount = ChunkCount; rqptr->rqBody.ChunkSize = ChunkSize; rqptr->rqBody.ChunkState = ChunkState; rqptr->NetIoPtr->ReadStatus = SS$_NORMAL; } /*****************************************************************************/ /* Processes an RFC2068 (HTTP/1.1) chunked body trailer. Returns the chunk processing state or zero to indicate a fatal error. */ int BodyTransferChunkedTrailer ( REQUEST_STRUCT *rqptr, char *DataPtr, int DataCount ) { int cnt, retval, tbcnt, ChunkState; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) { WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyTransferChunkedTrailer() !UL !UL", rqptr->rqBody.ChunkedTrailerBufferCount, DataCount); WatchDataDump (DataPtr, DataCount); } if (!rqptr->rqBody.ChunkedTrailerBufferPtr) { /* create the trailer buffer */ rqptr->rqBody.ChunkedTrailerBufferCount = rqptr->rqBody.ChunkedTrailerNewLineCount = 0; /* allow for a little 'elbow room' in the buffer */ rqptr->rqBody.ChunkedTrailerBufferSize = NetReadBufferSize - 4; rqptr->rqBody.ChunkedTrailerBufferPtr = VmGetHeap (rqptr, NetReadBufferSize); } cptr = DataPtr; cnt = DataCount; tbcnt = rqptr->rqBody.ChunkedTrailerBufferCount; sptr = rqptr->rqBody.ChunkedTrailerBufferPtr; zptr = sptr + rqptr->rqBody.ChunkedTrailerBufferSize; sptr += tbcnt; while (cnt && sptr < zptr) { if (*cptr == '\n') rqptr->rqBody.ChunkedTrailerNewLineCount++; else if (*cptr != '\r') rqptr->rqBody.ChunkedTrailerNewLineCount = 0; *sptr++ = *cptr++; tbcnt++; cnt--; if (rqptr->rqBody.ChunkedTrailerNewLineCount == 2) break; } if (sptr >= zptr) { BodyTransferChunkedError (rqptr, FI_LI); return (0); } *sptr = '\0'; rqptr->rqBody.ChunkedTrailerBufferCount = tbcnt; if (rqptr->rqBody.ChunkedTrailerNewLineCount == 2) ChunkState = CHUNK_STATE_END; else ChunkState = CHUNK_STATE_TRAILER; if (ChunkState == CHUNK_STATE_END) { if (WATCHING (rqptr, WATCH_REQUEST_BODY)) { WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "CHUNK trailer !UL bytes", rqptr->rqBody.ChunkedTrailerBufferCount); WatchDataDump (rqptr->rqBody.ChunkedTrailerBufferPtr, rqptr->rqBody.ChunkedTrailerBufferCount); } if (rqptr->rqBody.ChunkedTrailerBufferCount) { retval = RequestFields (rqptr, rqptr->rqBody.ChunkedTrailerBufferPtr, rqptr->rqBody.ChunkedTrailerBufferCount); if (retval < 0) { BodyTransferChunkedError (rqptr, FI_LI); return (0); } } } return (ChunkState); } /*****************************************************************************/ /* Generate an error and set the read status block to indicate. */ BodyTransferChunkedError ( REQUEST_STRUCT *rqptr, char *SourceModuleName, int SourceLineNumber ) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyTransferChunkedError()"); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_BODY_READ), SourceModuleName, SourceLineNumber); /* set the read status block to indicate an error */ rqptr->NetIoPtr->ReadStatus = SS$_ABORT; rqptr->NetIoPtr->ReadCount = 0; } /*****************************************************************************/ /* ZLIB inflate GZIP compressed data. Of course a single network read buffer full can provide multiple data buffer fulls. This requires that this function be called multiple times until the output buffer, sourced from the network read buffer, is emptied. Essentially this is done by using the data specified by the IO status block (NetIoPtr->ReadStatus and NetIoPtr->ReadCount) and what is left in the ZLIB input buffer (gzptr->InflateAvailIn dereived from ZLIB's avail_in). */ BodyContentGzip (REQUEST_STRUCT *rqptr) { int DataCount; char *DataPtr; GZIP_COMPRESS *gzptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyContentGzip() !&X !UL", rqptr->rqBody.DataPtr, rqptr->NetIoPtr->ReadCount); DataPtr = rqptr->rqBody.DataPtr; DataCount = rqptr->NetIoPtr->ReadCount; gzptr = &rqptr->GzipCompress; /* if the input stream is empty the read buffer will be fully consumed */ if (!gzptr->InflateAvailIn) rqptr->NetIoPtr->ReadCount = 0; if (!gzptr->InflateStartStream) { /* initialization */ if (GzipInflateBegin (rqptr, gzptr, rqptr->rqNet.ReadBufferSize)) { rqptr->rqBody.DataStatus = SS$_NORMAL; if (!DataCount) return; /* otherwise drop thru to begin processing the gzipped content */ } else { rqptr->rqBody.DataStatus = SS$_ABORT; rqptr->rqBody.DataCount = 0; return; } } if (GzipInflate (rqptr, gzptr, &DataPtr, &DataCount)) { rqptr->rqBody.DataStatus = SS$_NORMAL; rqptr->rqBody.DataCount = DataCount; rqptr->rqBody.DataPtr = DataPtr; } else { rqptr->rqBody.DataStatus = SS$_ABORT; rqptr->rqBody.DataCount = 0; } } /*****************************************************************************/ /* This is a special case. It reads the entire request body into a buffer. Needless-to-say the body length must be less than the allocated buffer space. It is intended only for the HTADMIN.C and UPD.C modules which like to get small request query strings as POSTs. This approach is a bit of a kludge in some ways but simplifies the parsing of these POSTed query strings significantly. */ void BodyProcessReadAll (REQUEST_STRUCT *rqptr) { BODY_PROCESS *prptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyProcessReadAll() !&F !&X !UL !&S", &BodyProcessReadAll, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount, rqptr->rqBody.DataStatus); if (!(prptr = rqptr->rqBody.ProcessPtr)) { /**************/ /* initialize */ /**************/ prptr = rqptr->rqBody.ProcessPtr = VmGetHeap (rqptr, sizeof(BODY_PROCESS)); rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_OTHER; /* just body buffer size */ prptr->BlockBufferSize = rqptr->rqBody.ContentLength64; prptr->BlockBufferPtr = VmGetHeap (rqptr, prptr->BlockBufferSize); prptr->BlockBufferCount = 0; } if (VMSnok (rqptr->rqBody.DataStatus)) { /*****************************/ /* network error/end-of-file */ /*****************************/ if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE) { rqptr->rqBody.DataPtr = prptr->BlockBufferPtr; rqptr->rqBody.DataCount = prptr->BlockBufferCount; /* as this will be considered null-terminated we'd better */ rqptr->rqBody.DataPtr[rqptr->rqBody.DataCount] = '\0'; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ); ErrorVmsStatus (rqptr, rqptr->rqBody.DataStatus, FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } if (prptr->BlockBufferCount + rqptr->rqBody.DataCount > prptr->BlockBufferSize) { /* woops, not big enough! */ rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ); ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } /* add what has just been received */ memcpy (prptr->BlockBufferPtr + prptr->BlockBufferCount, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount); prptr->BlockBufferCount += rqptr->rqBody.DataCount; BodyRead (rqptr); } /*****************************************************************************/ /* Supply the body data in whole 512 byte virtual blocks so it can be written to disk using block I/O (used by PUT.C module). */ void BodyProcessByVirtualBlock (REQUEST_STRUCT *rqptr) { int cnt, status; char *bptr; BODY_PROCESS *prptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyProcessByVirtualBlock() !&F !&X !UL !&S", &BodyProcessByVirtualBlock, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount, rqptr->rqBody.DataStatus); if (!(prptr = rqptr->rqBody.ProcessPtr)) { /**************/ /* initialize */ /**************/ prptr = rqptr->rqBody.ProcessPtr = VmGetHeap (rqptr, sizeof(BODY_PROCESS)); rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_VIRTUALBLOCK; BodyBufferAllocate (rqptr); } bptr = prptr->BlockBufferPtr + prptr->BlockBufferCount; if (prptr->BlockLeftOverCount) { /**********************************/ /* remains of previous processing */ /**********************************/ memcpy (bptr, prptr->BlockLeftOverPtr, prptr->BlockLeftOverCount); bptr += prptr->BlockLeftOverCount; prptr->BlockLeftOverCount = 0; prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr; } if (VMSnok (rqptr->rqBody.DataStatus)) { /*****************************/ /* network error/end-of-file */ /*****************************/ if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE) { BodyBufferEndOfFile (rqptr); SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ); ErrorVmsStatus (rqptr, rqptr->rqBody.DataStatus, FI_LI); SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } /*********************************/ /* populate virtual block buffer */ /*********************************/ /* add what has just been received */ memcpy (bptr, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount); prptr->BlockBufferCount += rqptr->rqBody.DataCount; BodyBufferWrite (rqptr); } /*****************************************************************************/ /* Decode URL-encoded byte stream. Eliminates form field names, field equal symbols and field-separating ampersands. Converts '+' characters in field content into spaces and hexadecimal, URL-encoded characters into the respective character. A field named "hexencoded" is expected to contain hexadecimal-encoded bytes, and has these two-digit numbers converted back into bytes before inclusion in the file. The presence of a non-null field named "previewonly" causes a temporary, delete-on-close file name to be generated (for previewing the POSTed file :^). A field "previewnote" contains text placed at the very beginning of a previewed file. This can either be left in place or eliminated by pointer manipulation depending on the value of "previewonly". Also see UPD.C module. Calls 'rqptr->rqBody.AstFunction' with storage set as described above in the prologue to this module. When all data has been processed and passed on it returns SS$_ENDOFFILE. Provides data in 512 byte virtual blocks as described in the prologue. */ void BodyProcessUrlEncoded (REQUEST_STRUCT *rqptr) { int cnt, status; unsigned char ch; char *bptr, *cptr, *sptr, *zptr; BODY_PROCESS *prptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyProcessUrlEncoded() !&F !&X !UL !&S", &BodyProcessUrlEncoded, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount, rqptr->rqBody.DataStatus); if (!(prptr = rqptr->rqBody.ProcessPtr)) { /**************/ /* initialize */ /**************/ prptr = rqptr->rqBody.ProcessPtr = VmGetHeap (rqptr, sizeof(BODY_PROCESS)); rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_URLENCODED; prptr->UrlEncodedFieldNameSize = sizeof(prptr->UrlEncodedFieldName); prptr->UrlEncodedFieldValueSize = sizeof(prptr->UrlEncodedFieldValue); prptr->UrlEncodedFieldNameCount = prptr->UrlEncodedFieldValueCount = 0; prptr->UrlEncodedFieldName[0] = prptr->UrlEncodedFieldValue[0] = '\0'; /* this is to kick off the field name processing, it's ignored */ prptr->UrlEncodedFieldName[prptr->UrlEncodedFieldNameCount++] = '!'; BodyBufferAllocate (rqptr); } bptr = prptr->BlockBufferPtr + prptr->BlockBufferCount; if (prptr->BlockLeftOverCount) { /**********************************/ /* remains of previous processing */ /**********************************/ memcpy (bptr, prptr->BlockLeftOverPtr, prptr->BlockLeftOverCount); bptr += prptr->BlockLeftOverCount; prptr->BlockLeftOverCount = 0; prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr; } if (VMSnok (rqptr->rqBody.DataStatus)) { /*****************************/ /* network error/end-of-file */ /*****************************/ if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE) { BodyBufferEndOfFile (rqptr); SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ); ErrorVmsStatus (rqptr, rqptr->rqBody.DataStatus, FI_LI); SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } /******************/ /* decode content */ /******************/ cptr = rqptr->rqBody.DataPtr; cnt = rqptr->rqBody.DataCount; while (cnt) { if (prptr->UrlDecodeIdx && prptr->UrlDecodeIdx < 3) { prptr->UrlDecode[prptr->UrlDecodeIdx++] = *cptr++; cnt--; if (prptr->UrlDecodeIdx < 3) continue; } else if (*cptr == '%') { prptr->UrlDecode[prptr->UrlDecodeIdx++] = *cptr++; cnt--; continue; } else if (*cptr == '=') { prptr->UrlEncodedFieldNameCount = 0; /* this is to kick off the field value processing, it's ignored */ prptr->UrlEncodedFieldValue[prptr->UrlEncodedFieldValueCount++] = '!'; cnt--; cptr++; continue; } else if (*cptr == '&') { prptr->UrlEncodedFieldValueCount = 0; prptr->UrlEncodedFieldHexEncoded = prptr->UrlEncodedHiddenLF = false; /* this is to kick off the field name processing, it's ignored */ prptr->UrlEncodedFieldName[prptr->UrlEncodedFieldNameCount++] = '!'; cnt--; cptr++; continue; } else { cnt--; ch = *cptr++; } if (prptr->UrlDecodeIdx) { ch = 0; if (prptr->UrlDecode[1] >= '0' && prptr->UrlDecode[1] <= '9') ch = (prptr->UrlDecode[1] - (int)'0') << 4; else if (TOUP(prptr->UrlDecode[1]) >= 'A' && TOUP(prptr->UrlDecode[1]) <= 'F') ch = (TOUP(prptr->UrlDecode[1]) - (int)'A' + 10) << 4; else { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } if (prptr->UrlDecode[2] >= '0' && prptr->UrlDecode[2] <= '9') ch += (prptr->UrlDecode[2] - (int)'0'); else if (TOUP(prptr->UrlDecode[2]) >= 'A' && TOUP(prptr->UrlDecode[2]) <= 'F') ch += (TOUP(prptr->UrlDecode[2]) - (int)'A' + 10); else { if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "URL-ENC-ERROR: !3AZ !&Z", prptr->UrlDecode, cptr); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } prptr->UrlDecode[prptr->UrlDecodeIdx=0] = '\0'; } else if (ch == '+') ch = ' '; /* absorb carriage returns for a stream-LF file */ if (ch == '\r') continue; /*******************/ /* process content */ /*******************/ if (prptr->UrlEncodedFieldNameReserved && prptr->UrlEncodedFieldNameCount) { /* starting a new field name after processing a reserved name/value */ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!&Z !&Z", prptr->UrlEncodedFieldName, prptr->UrlEncodedFieldValue); if (strsame (prptr->UrlEncodedFieldName+1, "success", -1)) ResponseLocation (rqptr, prptr->UrlEncodedFieldValue+1, prptr->UrlEncodedFieldValueCount); else if (strsame (prptr->UrlEncodedFieldName+1, "protection", -1)) { /* file protection */ strzcpy (prptr->ProtectionHexString, prptr->UrlEncodedFieldValue+1, sizeof(prptr->ProtectionHexString)); } else if (strsame (prptr->UrlEncodedFieldName+1, "previewonly", -1)) { /* if value is non-empty then preview, otherwise ignore */ if (prptr->UrlEncodedFieldValue[1]) prptr->PreviewOnly = true; } else if (strsame (prptr->UrlEncodedFieldName+1, "previewnote", -1)) { /* preview note (can be a maximum of 254 bytes) */ if (prptr->PreviewOnly) { for (sptr = prptr->UrlEncodedFieldValue+1; *sptr; *sptr++) if (*sptr == '^') /* ^ */ *bptr++ = '\n'; else *bptr++ = *sptr; } } else if (strsame (prptr->UrlEncodedFieldName+1, "fab$b_rat", -1)) { prptr->UrlEncodedFabRat = atoi(prptr->UrlEncodedFieldValue+1); if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "FAB$B_RAT 0x!2XL", prptr->UrlEncodedFabRat); } else if (strsame (prptr->UrlEncodedFieldName+1, "fab$b_rfm", -1)) { prptr->UrlEncodedFabRfm = atoi(prptr->UrlEncodedFieldValue+1); if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "FAB$B_RFM 0x!2XL", prptr->UrlEncodedFabRfm); } /* no longer processing the reserved name/value */ prptr->UrlEncodedFieldNameReserved = false; } if (prptr->UrlEncodedFieldNameCount) { /* currently buffering a field name */ zptr = prptr->UrlEncodedFieldName + prptr->UrlEncodedFieldNameSize; sptr = prptr->UrlEncodedFieldName + prptr->UrlEncodedFieldNameCount; if (sptr < zptr) *sptr++ = ch; if (sptr >= zptr) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneralOverflow (rqptr, FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } *sptr = '\0'; prptr->UrlEncodedFieldNameCount = sptr - prptr->UrlEncodedFieldName; continue; } if (prptr->UrlEncodedFieldValueCount == 1) { /* just finished buffering a field name, starting on a value */ prptr->UrlEncodedFieldHexEncoded = false; if (strsame (prptr->UrlEncodedFieldName+1, "success", -1)) prptr->UrlEncodedFieldNameReserved = true; else if (strsame (prptr->UrlEncodedFieldName+1, "protection", -1)) prptr->UrlEncodedFieldNameReserved = true; else if (strsame (prptr->UrlEncodedFieldName+1, "previewonly", -1)) prptr->UrlEncodedFieldNameReserved = true; else if (strsame (prptr->UrlEncodedFieldName+1, "previewnote", -1)) prptr->UrlEncodedFieldNameReserved = true; else if (strsame (prptr->UrlEncodedFieldName+1, "hexencoded", 10)) prptr->UrlEncodedFieldHexEncoded = true; else if (strsame (prptr->UrlEncodedFieldName+1, "fab$b_rat", -1)) prptr->UrlEncodedFieldNameReserved = true; else if (strsame (prptr->UrlEncodedFieldName+1, "fab$b_rfm", -1)) prptr->UrlEncodedFieldNameReserved = true; else prptr->UrlEncodedFieldNameReserved = false; /* With constraints on CDATA entities for field values this contains a '^' (^) substituted for any required line-feed. */ if (strsame (prptr->UrlEncodedFieldName+1, "hidden$lf", -1)) prptr->UrlEncodedHiddenLF = true; if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!&Z !&B", prptr->UrlEncodedFieldName, prptr->UrlEncodedFieldNameReserved); if (!prptr->UrlEncodedFieldNameReserved) prptr->UrlEncodedFieldNameCount = prptr->UrlEncodedFieldValueCount = 0; } if (prptr->UrlEncodedFieldNameReserved) { /* currently buffering a reserved field value */ zptr = prptr->UrlEncodedFieldValue + prptr->UrlEncodedFieldValueSize; sptr = prptr->UrlEncodedFieldValue + prptr->UrlEncodedFieldValueCount; if (sptr < zptr) *sptr++ = ch; if (sptr >= zptr) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneralOverflow (rqptr, FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } *sptr = '\0'; prptr->UrlEncodedFieldValueCount = sptr - prptr->UrlEncodedFieldValue; continue; } if (prptr->UrlEncodedHiddenLF && ch == '^') /* ^ */ ch = '\n'; else if (prptr->UrlEncodedFieldHexEncoded) { prptr->HexDecode[prptr->HexDecodeIdx++] = ch; if (prptr->HexDecodeIdx == 1) continue; prptr->HexDecodeIdx = 0; ch = 0; if (prptr->HexDecode[0] >= '0' && prptr->HexDecode[0] <= '9') ch = (prptr->HexDecode[0] - (int)'0') << 4; else if (TOUP(prptr->HexDecode[0]) >= 'A' && TOUP(prptr->HexDecode[0]) <= 'F') ch = (TOUP(prptr->HexDecode[0]) - (int)'A' + 10) << 4; else { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } if (prptr->HexDecode[1] >= '0' && prptr->HexDecode[1] <= '9') ch += (prptr->HexDecode[1] - (int)'0'); else if (TOUP(prptr->HexDecode[1]) >= 'A' && TOUP(prptr->HexDecode[1]) <= 'F') ch += (TOUP(prptr->HexDecode[1]) - (int)'A' + 10); else { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } } /* write the character into the virtual block buffer */ *bptr++ = ch; } /****************/ /* post process */ /****************/ /* save the current buffer count */ prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr; BodyBufferWrite (rqptr); } /*****************************************************************************/ /* Decode a "multipart/form-data" content-type request body. This will (or at least should) comprise one of more MIME parts with data of various types. Returns SS$_BADPARAM if it can't understand the stream format, SS$_RESULTOVF if buffer space is exhausted at any time. Otherwise a normal status. Calls 'rqptr->rqBody.AstFunction' with storage set as described above in the prologue to this module. When all data has been processed and passed on it returns SS$_ENDOFFILE. Provides data in 512 byte virtual blocks as described in the prologue. */ void BodyProcessMultipartFormData (REQUEST_STRUCT *rqptr) { int cnt, idx, status; char *bptr, *cptr, *sptr, *zptr; BODY_PROCESS *prptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyProcessMultipartFormData() !&F !&X !UL !&S", &BodyProcessMultipartFormData, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount, rqptr->rqBody.DataStatus); if (!(prptr = rqptr->rqBody.ProcessPtr)) { /**************/ /* initialize */ /**************/ if (!ConfigSameContentType (rqptr->rqHeader.ContentTypePtr, "multipart/form-data", -1)) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PUT_MULTIPART_FORMDATA), FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } prptr = rqptr->rqBody.ProcessPtr = VmGetHeap (rqptr, sizeof(BODY_PROCESS)); rqptr->rqBody.ProcessedAs = BODY_PROCESSED_AS_MULTIPART_FORMDATA; BodyBufferAllocate (rqptr); if (!BodyGetMimeBoundary (rqptr)) { /* error message generated by the above function */ rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } /* prepend these for boundary matching convenience only */ prptr->BoundaryBuffer[prptr->BoundaryIdx++] = '\r'; prptr->BoundaryBuffer[prptr->BoundaryIdx++] = '\n'; } bptr = prptr->BlockBufferPtr + prptr->BlockBufferCount; if (prptr->BlockLeftOverCount) { /**********************************/ /* remains of previous processing */ /**********************************/ memcpy (bptr, prptr->BlockLeftOverPtr, prptr->BlockLeftOverCount); bptr += prptr->BlockLeftOverCount; prptr->BlockLeftOverCount = 0; prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr; } if (VMSnok (rqptr->rqBody.DataStatus)) { /*****************************/ /* network error/end-of-file */ /*****************************/ if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE) { BodyBufferEndOfFile (rqptr); SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ); ErrorVmsStatus (rqptr, rqptr->rqBody.DataStatus, FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } /******************/ /* decode content */ /******************/ cptr = rqptr->rqBody.DataPtr; cnt = rqptr->rqBody.DataCount; while (cnt) { if (prptr->MimeHeaderIdx) { /***************************/ /* buffering a MIME header */ /***************************/ if (prptr->MimeHeaderIdx >= sizeof(prptr->MimeHeaderBuffer)) { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART); ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } if (*cptr == '\n') { /* check for the blank line terminating the MIME header */ if (prptr->MimeHeaderIdx >= 1 && prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx-1] == '\n') { /* end of MIME header */ cptr++; cnt--; prptr->MimeHeaderIdx--; prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx] = '\0'; prptr->MimeHeaderCount = prptr->MimeHeaderIdx; prptr->MimeHeaderIdx = 0; continue; } if (prptr->MimeHeaderIdx >= 3 && prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx-1] == '\r' && prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx-2] == '\n' && prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx-3] == '\r') { /* end of MIME header */ cptr++; cnt--; prptr->MimeHeaderIdx -= 3; prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx] = '\0'; prptr->MimeHeaderCount = prptr->MimeHeaderIdx; prptr->MimeHeaderIdx = 0; continue; } } prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx++] = *cptr++; cnt--; continue; } if (prptr->MimeHeaderBuffer[0]) { /****************************/ /* process MIME part header */ /****************************/ if (WATCHING (rqptr, WATCH_REQUEST_BODY)) { int Length; sptr = prptr->MimeHeaderBuffer; Length = prptr->MimeHeaderCount; if (SAME2(sptr,'\r\n')) { sptr += 2; Length -= 2; } WatchThis (WATCHITM(rqptr), WATCH_REQUEST_BODY, "MULTIPART"); WatchData (sptr, Length); } status = BodyProcessMultipartMimeHeader (rqptr); if (VMSnok (status)) { /* error message generated by the above function */ rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } /* finished with this part header */ prptr->MimeHeaderCount = 0; prptr->MimeHeaderBuffer[0] = '\0'; /* continue on to buffer the data */ } if (!prptr->BoundaryIdx) { /*********************************/ /* looking for start of boundary */ /*********************************/ if (*cptr == '\r') { prptr->BoundaryBuffer[prptr->BoundaryIdx++] = *cptr++; cnt--; continue; } /* just drop out the bottom as another octet */ } else { /*******************************/ /* still matching for boundary */ /*******************************/ if (!prptr->MultipartBoundary[prptr->BoundaryIdx]) { /*******************/ /* boundary match! */ /*******************/ /* reached end of the content-type boundary */ prptr->BoundaryIdx = 0; prptr->MimeHeaderBuffer[prptr->MimeHeaderIdx++] = *cptr++; cnt--; /* new boundary, post-process any required form data field */ if (prptr->MultipartFormDataPtr) { if (WATCHMOD (rqptr, WATCH_MOD_BODY)) { WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "FORM-DATA"); WatchDataFormatted ("!&Z\n", prptr->MultipartFormDataPtr); } if (prptr->MultipartFormDataPtr == prptr->MultipartSuccessUrl) ResponseLocation (rqptr, prptr->MultipartFormDataPtr, prptr->MultipartFormDataCount); } /* set these to NULL and empty to stop data copying */ prptr->MultipartContentTypePtr = prptr->MultipartFormDataPtr = prptr->MultipartFormDataCurrentPtr = NULL; continue; } if (*cptr == prptr->MultipartBoundary[prptr->BoundaryIdx]) { /* still matching */ prptr->BoundaryBuffer[prptr->BoundaryIdx++] = *cptr++; prptr->BoundaryBuffer[prptr->BoundaryIdx] = '\0'; cnt--; continue; } /********************/ /* match has failed */ /********************/ if (prptr->MultipartFormDataCurrentPtr) { /* restore match buffer to form data buffer */ for (idx = 0; idx < prptr->BoundaryIdx; idx++) { if (prptr->MultipartFormDataCount > prptr->MultipartFormDataSize) { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART); ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } if (isprint(prptr->BoundaryBuffer[idx]) && NOTEOL(prptr->BoundaryBuffer[idx])) *prptr->MultipartFormDataCurrentPtr++ = prptr->BoundaryBuffer[idx]; prptr->MultipartFormDataCount++; } *prptr->MultipartFormDataCurrentPtr = '\0'; } else { /* restore match buffer to content buffer */ if (prptr->MultipartContentTypePtr) for (idx = 0; idx < prptr->BoundaryIdx; idx++) *bptr++ = prptr->BoundaryBuffer[idx]; } prptr->BoundaryIdx = 0; prptr->BoundaryBuffer[0] = '\0'; /* If a then restart the matching algorithm using the current character. This will allow for correct behaviour with the likes of ---..boundary sequences (yup, got caught!) If not a then drop through to treat as just another octet. */ if (*cptr == '\r') continue; } /**********************/ /* just another octet */ /**********************/ if (prptr->MultipartFormDataCurrentPtr) { if (prptr->MultipartFormDataCount > prptr->MultipartFormDataSize) { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART); ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI); rqptr->rqBody.DataStatus = SS$_ABORT; SysDclAst (rqptr->rqBody.AstFunction, rqptr); return; } if (isprint(*cptr) && NOTEOL(*cptr)) *prptr->MultipartFormDataCurrentPtr++ = *cptr; } else /* if a content-type has been resolved then we're buffering data */ if (prptr->MultipartContentTypePtr) *bptr++ = *cptr; cptr++; cnt--; } /****************/ /* post process */ /****************/ /* save the current buffer count */ prptr->BlockBufferCount = bptr - prptr->BlockBufferPtr; BodyBufferWrite (rqptr); } /*****************************************************************************/ /* A "multipart/form-data" MIME part header has been read into a buffer. This can comprose serveral lines of header-type information termiated by a NUL character. Scan through this text isolating field of interest. In particular 'uploadfilename', 'protection' and 'name'. These are used by the PUT.C and PROXYFTP.C modules. */ int BodyProcessMultipartMimeHeader (REQUEST_STRUCT *rqptr) { char *cptr, *sptr, *zptr, *NamePtr; BODY_PROCESS *prptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyProcessMultipartMimeHeader()"); prptr = rqptr->rqBody.ProcessPtr; cptr = prptr->MimeHeaderBuffer; if (!SAME2(cptr,'\r\n')) { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART); ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI); return (SS$_BUGCHECK); } cptr += 2; if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchDataFormatted ("!&Z\n", cptr); while (*cptr) { while (*cptr && ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; if (strsame (cptr, "Content-Disposition:", 20)) { if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "Content-Disposition:"); /* set these to NULL and empty to stop data copying */ prptr->MultipartContentTypePtr = prptr->MultipartFormDataPtr = prptr->MultipartFormDataCurrentPtr = NULL; prptr->MultipartFormDataSize = prptr->MultipartFormDataCount = 0; cptr += 20; while (*cptr && NOTEOL(*cptr)) { if (strsame (cptr, "name=", 5)) { cptr += 5; if (*cptr == '\"') { cptr++; NamePtr = cptr; while (*cptr && *cptr != '\"' && NOTEOL(*cptr)) cptr++; if (*cptr != '\"') { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART); ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI); return (SS$_BUGCHECK); } } else { NamePtr = cptr; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; if (!ISLWS(*cptr)) { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART); ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI); return (SS$_BUGCHECK); } } *cptr++ = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!&Z", NamePtr); if (strsame (NamePtr, "name", -1)) { /* handled by the storing the associated 'filename=".."' */ } else if (strsame (NamePtr, "protection", -1)) { prptr->MultipartFormDataPtr = prptr->ProtectionHexString; prptr->MultipartFormDataSize = sizeof(prptr->ProtectionHexString); } else if (strsame (NamePtr, "success", -1)) { prptr->MultipartFormDataPtr = prptr->MultipartSuccessUrl; prptr->MultipartFormDataSize = sizeof(prptr->MultipartSuccessUrl); } else if (strsame (NamePtr, "type", -1)) { prptr->MultipartFormDataPtr = prptr->FtpFileType; prptr->MultipartFormDataSize = sizeof(prptr->FtpFileType); } else if (strsame (NamePtr, "uploadfilename", -1)) { prptr->MultipartFormDataPtr = prptr->MultipartUploadFileName; prptr->MultipartFormDataSize = sizeof(prptr->MultipartUploadFileName); } /* anything else is just ignored */ prptr->MultipartFormDataCurrentPtr = prptr->MultipartFormDataPtr; prptr->MultipartFormDataCount = 0; } else if (strsame (cptr, "filename=", 9)) { zptr = (sptr = prptr->MultipartFileName) + sizeof(prptr->MultipartFileName); cptr += 9; if (*cptr == '\"') { cptr++; while (*cptr && *cptr != '\"' && NOTEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++; if (*cptr != '\"') { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART); ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI); return (SS$_BUGCHECK); } } else { while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++; if (!ISLWS(*cptr)) { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART); ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI); return (SS$_BUGCHECK); } } *sptr = '\0'; prptr->MultipartFormDataFileNameCount++; if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!&Z (!UL)", prptr->MultipartFileName, prptr->MultipartFormDataFileNameCount); /* only one file per upload please! */ if (prptr->MultipartFormDataFileNameCount > 1) { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART); rqptr->rqResponse.ErrorOtherTextPtr = "Only one file name per upload please!"; ErrorVmsStatus (rqptr, SS$_BADPARAM, FI_LI); return (SS$_BADPARAM); } } while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; while (*cptr && ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; } } else if (strsame (cptr, "Content-Type:", 13)) { if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "Content-Type:"); cptr += 13; while (*cptr && ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; /* find length of content-type */ for (sptr = cptr; *sptr && !ISLWS(*sptr) && NOTEOL(*sptr); sptr++); prptr->MultipartContentTypePtr = sptr = VmGetHeap (rqptr, sptr-cptr+1); while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr)) *sptr++ = *cptr++; *sptr = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!&Z", prptr->MultipartContentTypePtr); } else if (WATCHMOD (rqptr, WATCH_MOD_BODY)) { for (sptr = cptr; *sptr && NOTEOL(*sptr); sptr++); WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "{!UL}!-!#AZ", sptr-cptr, cptr); } /* scan to start of next "line" */ while (*cptr && NOTEOL(*cptr)) cptr++; while (*cptr && ISEOL(*cptr)) cptr++; } return (SS$_NORMAL); } /*****************************************************************************/ /* Scan through the request's "Content-Type:" header field looking for a MIME "boundary=" string. */ int BodyGetMimeBoundary (REQUEST_STRUCT *rqptr) { char *cptr, *sptr, *zptr; BODY_PROCESS *prptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyGetMimeBoundary() !&Z", rqptr->rqHeader.ContentTypePtr); prptr = rqptr->rqBody.ProcessPtr; prptr->MultipartBoundary[0] = '\0'; prptr->MultipartBoundaryLength = 0; cptr = rqptr->rqHeader.ContentTypePtr; while (*cptr) { while (*cptr && *cptr != ';') cptr++; if (!*cptr) break; cptr++; while (*cptr && ISLWS(*cptr)) cptr++; if (strsame (cptr, "boundary=", 9)) { cptr += 9; zptr = (sptr = prptr->MultipartBoundary) + sizeof(prptr->MultipartBoundary); /* prepend these only for matching convenience */ *sptr++ = '\r'; *sptr++ = '\n'; *sptr++ = '-'; *sptr++ = '-'; while (*cptr && *cptr != ';' && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) sptr = prptr->MultipartBoundary; *sptr = '\0'; prptr->MultipartBoundaryLength = sptr - prptr->MultipartBoundary; } } if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!&Z", prptr->MultipartBoundary + 4); if (!prptr->MultipartBoundaryLength) { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PUT_MULTIPART); rqptr->rqResponse.ErrorOtherTextPtr = "MIME boundary not found!"; ErrorVmsStatus (rqptr, SS$_BUGCHECK, FI_LI); } return (prptr->MultipartBoundaryLength); } /*****************************************************************************/ /* Allocate a multi-block buffer. */ void BodyBufferAllocate (REQUEST_STRUCT *rqptr) { BODY_PROCESS *prptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyBufferAllocate()"); prptr = rqptr->rqBody.ProcessPtr; prptr->MultiBlockCount = BodyMultiBlockCount; /* should never, ever happen but we need at least this much space */ if (prptr->MultiBlockCount * 512 < rqptr->rqBody.DataSize) prptr->MultiBlockCount = (rqptr->rqBody.DataSize / 512) + 1; /* allow sufficient extra buffer to a full network read */ prptr->BlockBufferSize = (prptr->MultiBlockCount * 512) + rqptr->rqBody.DataSize; prptr->BlockBufferPtr = VmGetHeap (rqptr, prptr->BlockBufferSize); prptr->BlockBufferCount = prptr->BlockLeftOverCount = 0; prptr->BlockBufferVBN = 1; if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "MULTIBLOCK !UL", prptr->MultiBlockCount); } /*****************************************************************************/ /* The file buffer is larger than the network data buffer and so with larger files fills incrementally over multiple reads. Check if the file buffer is at capacity by calculating the number of virtual blocks contained in it. If less than the multiblock number read more. When enough virtual blocks have accumulated for an efficient write, calculate what remains and note that. Write the number of virtual blocks which then ASTs to read more. Each time the read AST delivers that routine copies into the file buffer the data that remained before copying the just-read data. */ void BodyBufferWrite (REQUEST_STRUCT *rqptr) { int BytesLeftOver, VirtualBlocks; BODY_PROCESS *prptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyBufferWrite() !UL !UL", rqptr->rqBody.ProcessPtr->BlockBufferCount, rqptr->rqBody.ProcessPtr->BlockBufferCount / 512); prptr = rqptr->rqBody.ProcessPtr; VirtualBlocks = prptr->BlockBufferCount / 512; if (VirtualBlocks >= prptr->MultiBlockCount) { VirtualBlocks = prptr->MultiBlockCount; BytesLeftOver = prptr->BlockBufferCount - VirtualBlocks * 512; prptr->BlockBufferCount = VirtualBlocks * 512; if (prptr->BlockLeftOverCount = BytesLeftOver) prptr->BlockLeftOverPtr = prptr->BlockBufferPtr + prptr->BlockBufferCount; } else VirtualBlocks = 0; if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "!UL !UL !UL !UL", prptr->BlockBufferCount, prptr->BlockBufferVBN, VirtualBlocks, BytesLeftOver); if (VirtualBlocks) { rqptr->rqBody.DataVBN = prptr->BlockBufferVBN; rqptr->rqBody.DataPtr = prptr->BlockBufferPtr; rqptr->rqBody.DataCount = prptr->BlockBufferCount; rqptr->rqBody.DataStatus = SS$_NORMAL; prptr->ProcessedByteCount += prptr->BlockBufferCount; prptr->BlockBufferVBN += VirtualBlocks; /* now the buffer count can be reset */ prptr->BlockBufferCount = 0; SysDclAst (rqptr->rqBody.AstFunction, rqptr); } else BodyRead (rqptr); } /*****************************************************************************/ /* Handle still-buffered data when end-of-file is encountered. */ void BodyBufferEndOfFile (REQUEST_STRUCT *rqptr) { BODY_PROCESS *prptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_BODY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_BODY, "BodyBufferEndOfFile()"); prptr = rqptr->rqBody.ProcessPtr; if (prptr->BlockBufferCount) { rqptr->rqBody.DataVBN = prptr->BlockBufferVBN; rqptr->rqBody.DataPtr = prptr->BlockBufferPtr; rqptr->rqBody.DataCount = prptr->BlockBufferCount; rqptr->rqBody.DataStatus = SS$_NORMAL; prptr->ProcessedByteCount += prptr->BlockBufferCount; prptr->BlockBufferCount = 0; } else rqptr->rqBody.DataStatus = SS$_ENDOFFILE; } /*****************************************************************************/