/*****************************************************************************/ /* Put.c PUT, POST or DELETE a document. Although the HTTP/1.1 specification differentiates between the PUT and POST functionality this module does not. Allows documents (files) and directories (i.e. specification ending in a slash) to be created. Either the DELETE method or a kludge allows these to be deleted. The kludge: if a wildcard version (";*") is included with the specification the respective file or directory is deleted with either of the non-DELETE methods. Access Control -------------- Relies on HTTPd authentication. If a remote username has not been verified an automatic no-privilege error is generated. Access to create or delete documents is also controlled by any permissions/protections/ACLs, etc. on the parent directory controlling access by the HTTPd server account (usually HTTP$SERVER). The explicit action required to grant the server account access to a directory it needs to write into is a bit of a nuisance, but deemed a good double check, preventing access due to flawed authentication/authorization configuration (and hopefully even a range of possible design problems or coding errors within this server :^) Setting ACLs is preferable to granting world write access (obviously): This ACE would grant PUT, POST or DELETE access to the server: $ SET ACL directory.DIR /ACL=(IDENT=HTTP$SERVER,ACCESS=READ+WRITE) This ACE would explcitly deny POST access to the server: $ SET ACL directory.DIR /ACL=(IDENT=HTTP$SERVER,ACCESS=READ) This ACE would explcitly deny ALL access to the server: $ SET ACL directory.DIR /ACL=(IDENT=HTTP$SERVER,ACCESS=NONE) BE ULTRA-CAUTIOUS ... check access to directory before undertaking any action that will alter anything within it, even if doing that potentially involves some redundant processesing/checking! In this way any programming oversights will hopefully be ameliorated. File Record Format ------------------ Text files are created with a STREAM_LF format and CR implied carriage-control. Binary files are created in undefined (UDF) record format with no record attributes (carriage-control). File writing is done using block I/O for efficiency and record independence. Content-Type: application/x-www-form-urlencoded ----------------------------------------------- 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. This processing is performed by BodProcessUrlEncoded(). Content-Type: multipart/form-data --------------------------------- 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". in the file. This processing is performed by BodProcessMultipartFormData(). Other Considerations -------------------- PUT/POSTed files automatically have a three version limit imposed. If an error occurs (message generated) a file created by the request is deleted. WebDAV Support -------------- The PUT module was essentially suitable for WebDAV support. Only minor modifications were made to support WebDAV's authorization model, agent idiosyncracies, etc, and to shield WebDAV requests from the historical idiosyncracies of WASD's PUT method support :-) VERSION HISTORY --------------- 22-NOV-2020 MGD content length now 64 bit 01-NOV-2018 MGD bugfix; PutWriteFileOpen() override incompatible existing file characteristics by first erasing the file 06-JUL-2014 MGD PutWebDavBegin() migrate into DavWebPutBegin() bugfix; PutWriteFileOpen() WebDAV should not use default protection mask and instead propagate from profile 17-AUG-2013 MGD PutWriteFileOpen() support FAB$C_STM and FAB$C_STMCR 10-MAY-2010 JPP bugfix; PutWriteFileOpen() ensure SYSPRV enabled before $ERASE() if not WebDAV request (for access and ownership) 24-MAR-2010 MGD bugfix; PutWriteFileOpen() ensure SYSPRV enabled before $CREATE() if not WebDAV request (for access and ownership) 21-JUN-2009 MGD PutWriteFileOpen() allow record format of binary files to be specified by global configuration and path setting 01-MAY-2007 MGD WebDAV considerations, "If-Match:.." and "If-Not-Match:.." conditional processing, pre-allocate space if the size is fixed (viz. not a form), FAB and RAB flags to enable truncate-on-put, PutBegin() remove 'SpecificDirectory' parameter 30-OCT-2004 MGD bugfix; PutWriteFileOpen() always check 'prptr' non-NULL 24-JUL-2004 MGD bugfix; (potential anyway) PutWriteFileClose()/PutEnd(), in the absence of a content-type assume bag-o'-bytes 10-JAN-2004 MGD PutWriteFileOpen() 'delete-on-close' file specification extended to include a four digit global 'uniquifier' (with faster systems and multiple instances it was entirely possible that purely a time-based name might be inadequite) 23-AUG-2002 MGD set fab$b_rfm and fab$b_rat (is supplied with body) to establish file attributes (falling back to type analysis) 02-FEB-2002 MGD rework file processing for request body processing changes 04-AUG-2001 MGD support module WATCHing 04-JUL-2000 MGD redirect from POST (success=) 04-JAN-2000 MGD support ODS-2 and ODS-5 using ODS module 12-MAR-1998 MGD file protection may now be specified (as hexadecimal value) 25-SEP-1997 MGD bugfix; PutProcessText() removed CRLF munging 17-AUG-1997 MGD message database, SYSUAF-authenticated users security-profile 27-MAR-1997 MGD provide file edit preview (see UPD.C) 01-FEB-1997 MGD HTTPd version 4 01-SEP-1996 MGD provide "Content-Type: multipart/form-data" for file upload 06-APR-1996 MGD initial development */ /*****************************************************************************/ #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 /* standard C header files */ #include #include #include /* VMS related header files */ #include #include #include #include #include #include #include #include #include #include #include /* application header files */ #include "wasd.h" #define WASD_MODULE "PUT" /***************/ /* definitions */ /***************/ #define PUT_FILE 0x01 #define PUT_DIRECTORY 0x02 #define PUT_UPLOAD 0x04 #define PUT_CREATED 0x10 #define PUT_DELETED 0x20 #define PUT_SUPERCEDED 0x40 /******************/ /* global storage */ /******************/ BOOL PutOnlyTextFilesStreamLf = true; /********************/ /* external storage */ /********************/ #ifdef DBUG extern BOOL Debug; #else #define Debug 0 #endif extern BOOL OdsExtended; extern unsigned long SysPrvMask[]; extern char SoftwareID[], ErrorSanityCheck[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern MSG_STRUCT Msgs; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Begin PUT processing. */ PutBegin ( REQUEST_STRUCT *rqptr, REQUEST_AST NextTaskFunction ) { PUT_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutBegin() !&A !@SQ !&Z", NextTaskFunction, rqptr->rqHeader.ContentLength64, rqptr->ParseOds.NamDevicePtr); if (ERROR_REPORTED (rqptr)) { /* previous error, cause threaded processing to unravel */ SysDclAst (NextTaskFunction, rqptr); return; } if (!rqptr->AccountingDone++) InstanceGblSecIncrLong (&AccountingPtr->DoPutCount); /* authentication is mandatory for a PUT, DELETE or POST */ if (!rqptr->RemoteUser[0] || !((rqptr->rqAuth.RequestCan & HTTP_METHOD_PUT) || (rqptr->rqAuth.RequestCan & HTTP_METHOD_POST) || (rqptr->rqAuth.RequestCan & HTTP_METHOD_DELETE))) { rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; ErrorVmsStatus (rqptr, SS$_NOPRIV, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } /* set up the task structure (only ever one per request!) */ rqptr->PutTaskPtr = tkptr = (PUT_TASK*) VmGetHeap (rqptr, sizeof(PUT_TASK)); tkptr->NextTaskFunction = NextTaskFunction; OdsStructInit (&tkptr->SearchOds, false); if (rqptr->WebDavTaskPtr) tkptr->ProtectionMask = 0; else tkptr->ProtectionMask = PUT_DEFAULT_FILE_PROTECTION; if (rqptr->rqHeader.IfMatchPtr || rqptr->rqHeader.IfNoneMatchPtr) PutIfBegin (rqptr); else if (rqptr->WebDavTaskPtr) DavWebPutBegin (rqptr); else PutWasdBegin (rqptr); } /*****************************************************************************/ /* Begin the WASD-idiosyncratic PUT processing. */ PutWasdBegin (REQUEST_STRUCT *rqptr) { PUT_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutWasdBegin()"); tkptr = rqptr->PutTaskPtr; if ((rqptr->rqHeader.Method == HTTP_METHOD_DELETE) || (rqptr->ParseOds.NamVersionPtr[0] == ';' && rqptr->ParseOds.NamVersionPtr[1] == '*')) { /**********************************************/ /* delete (or file/directory deletion kludge) */ /**********************************************/ /* terminate on either the directory or file name */ if (!rqptr->ParseOds.NamNameLength && !rqptr->ParseOds.NamTypeLength) rqptr->ParseOds.NamNamePtr[0] = '\0'; else rqptr->ParseOds.NamVersionPtr[0] = '\0'; if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "PUT !AZ/delete !AZ", rqptr->rqHeader.MethodName, rqptr->ParseOds.NamDevicePtr); PutDelete (rqptr); PutEnd (rqptr); return; } if (!rqptr->ParseOds.NamNameLength && !rqptr->ParseOds.NamTypeLength) { /********************/ /* create directory */ /********************/ /* multipart/form-data supplies the parent directory as the path */ if (!ConfigSameContentType (rqptr->rqHeader.ContentTypePtr, "multipart/", 10)) { if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "PUT !AZ/create !AZ", rqptr->rqHeader.MethodName, rqptr->ParseOds.NamDevicePtr); PutCreateDirectory (rqptr); PutEnd (rqptr); return; } } /* terminate on version for file name, name for directory */ if (!rqptr->ParseOds.NamNameLength && rqptr->ParseOds.NamTypeLength == 1 && rqptr->ParseOds.NamVersionLength == 1) *rqptr->ParseOds.NamNamePtr = '\0'; else *rqptr->ParseOds.NamVersionPtr = '\0'; if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "PUT !AZ/create !AZ as \"!AZ\"", rqptr->rqHeader.MethodName, rqptr->ParseOds.NamDevicePtr, rqptr->rqHeader.ContentTypePtr ? rqptr->rqHeader.ContentTypePtr : "(unspecified content-type)"); if (ConfigSameContentType (rqptr->rqHeader.ContentTypePtr, "application/x-www-form-urlencoded", -1)) BodyReadBegin (rqptr, &PutWriteFile, &BodyProcessUrlEncoded); else if (ConfigSameContentType (rqptr->rqHeader.ContentTypePtr, "multipart/", 10)) BodyReadBegin (rqptr, &PutWriteFile, &BodyProcessMultipartFormData); else { tkptr->ContentLengthFixed = true; BodyReadBegin (rqptr, &PutWriteFile, &BodyProcessByVirtualBlock); } } /*****************************************************************************/ /* If "If-Match: " or "If-Not-Match: " conditional processing has been requested then get the (possible) file details, generate entity data from those details, and continue processing (or not) according to the match. This function (re)calls itself multiple times during processing. */ PutIfBegin (REQUEST_STRUCT *rqptr) { int status; char EntityTag [32]; PUT_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutIfBegin()"); tkptr = rqptr->PutTaskPtr; if (!tkptr->SearchOds.Fab.fab$l_sts) { /* use SYSPRV to ensure access */ sys$setprv (1, &SysPrvMask, 0, 0); OdsParse (&tkptr->SearchOds, rqptr->ParseOds.ExpFileName, rqptr->ParseOds.ExpFileNameLength, NULL, 0, 0, &PutIfBegin, rqptr); sys$setprv (0, &SysPrvMask, 0, 0); return; } if (VMSnok(status = tkptr->SearchOds.Fab.fab$l_sts)) { OdsParseRelease (&tkptr->SearchOds); if (rqptr->WebDavTaskPtr) DavWebResponse (rqptr, 0, status, "entity tag", FI_LI); else ErrorVmsStatus (rqptr, status, FI_LI); PutEnd (rqptr); return; } if (!tkptr->SearchOds.FileQio.IOsb.Status) { /* use SYSPRV to ensure access */ sys$setprv (1, &SysPrvMask, 0, 0); OdsFileAcpInfo (&tkptr->SearchOds, &PutIfBegin, rqptr); sys$setprv (0, &SysPrvMask, 0, 0); return; } /* deassign the channel allocated by OdsFileAcpInfo() */ sys$dassgn (tkptr->SearchOds.FileQio.AcpChannel); if (VMSok(status = tkptr->SearchOds.FileQio.IOsb.Status)) { /* file exists, generate entity tag from the data */ OdsParseRelease (&tkptr->SearchOds); FileGenerateEntityTag (EntityTag, &tkptr->SearchOds.FileQio); } else { /* error accessing file */ OdsParseRelease (&tkptr->SearchOds); if (rqptr->WebDavTaskPtr) DavWebResponse (rqptr, 0, status, "entity tag", FI_LI); else ErrorVmsStatus (rqptr, status, FI_LI); PutEnd (rqptr); return; } if (!ResponseEntityMatch (rqptr, EntityTag)) { PutEnd (rqptr); return; } if (rqptr->WebDavTaskPtr) DavWebPutBegin (rqptr); else PutWasdBegin (rqptr); } /*****************************************************************************/ /* Conclude processing the request. If a temporary file name/path was generated then it's a preview only, turn the POST into a GET of the temporary file path. */ PutEnd (REQUEST_STRUCT *rqptr) { int status; PUT_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutEnd()"); tkptr = rqptr->PutTaskPtr; /* indicate the PUT task has concluded correctly */ rqptr->PutTaskPtr = NULL; if (tkptr->FileOpen) PutWriteFileClose (rqptr); SysDclAst (tkptr->NextTaskFunction, rqptr); } /*****************************************************************************/ /* Check the status result of the preceding request body network read or content processing using 'rqptr->rqBody.DataStatus'. Write (i.e. block I/O) the data represented by 'rqptr->rqBody.DataPtr' and 'rqptr->rqBody.DataCount'. For all writes except the possible final one this should be a number of complete virtual blocks (512 bytes) beginning at 'rqptr->rqBody.DataVBN'. The final one may be any number of bytes between 1 and 511. */ PutWriteFile (REQUEST_STRUCT *rqptr) { int status; PUT_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutWriteFile() !&F !&X !UL !UL !&S", &PutWriteFile, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount, rqptr->rqBody.DataVBN, rqptr->rqBody.DataStatus); tkptr = rqptr->PutTaskPtr; if (VMSok (rqptr->rqBody.DataStatus)) { if (!tkptr->FileOpen) { status = PutWriteFileOpen (rqptr); if (VMSnok (status)) return; } tkptr->FileSizeBytes += rqptr->rqBody.DataCount; tkptr->FileOds.Rab.rab$l_rbf = rqptr->rqBody.DataPtr; tkptr->FileOds.Rab.rab$w_rsz = rqptr->rqBody.DataCount; tkptr->FileOds.Rab.rab$l_bkt = rqptr->rqBody.DataVBN; sys$write (&tkptr->FileOds.Rab, &PutWriteFileAst, &PutWriteFileAst); return; } if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE) { /* body is exhausted (aren't we all?) */ if (!tkptr->FileOpen) { status = PutWriteFileOpen (rqptr); if (VMSnok (status)) return; } PutWriteFileClose (rqptr); PutEnd (rqptr); return; } /* error reading or processing request body */ if (rqptr->WebDavTaskPtr) if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PUT write !&S !AZ", rqptr->rqBody.DataStatus, tkptr->FileOds.ExpFileName); rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_REQUEST_BODY_READ); ErrorVmsStatus (rqptr, rqptr->rqBody.DataStatus, FI_LI); PutWriteFileClose (rqptr); PutEnd (rqptr); } /*****************************************************************************/ /* After each block I/O written this AST function is called to either write more data, or on conlusion to call the end file function. If an error is reported from the write the end file function is called, with the presence of the error message causing the file to be deleted. */ PutWriteFileAst (struct RAB *RabPtr) { int status; REQUEST_STRUCT *rqptr; PUT_TASK *tkptr; /*********/ /* begin */ /*********/ rqptr = RabPtr->rab$l_ctx; if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutWriteFileAst() !&F sts:!&X stv:!&X rsz:!UL", &PutWriteFileAst, RabPtr->rab$l_sts, RabPtr->rab$l_stv, RabPtr->rab$w_rsz); tkptr = rqptr->PutTaskPtr; if (VMSok (tkptr->FileOds.Rab.rab$l_sts)) { /* read more form the request body */ BodyRead (rqptr); return; } rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileName; ErrorVmsStatus (rqptr, tkptr->FileOds.Rab.rab$l_sts, FI_LI); PutWriteFileClose (rqptr); PutEnd (rqptr); } /*****************************************************************************/ /* Create a file using '->FileName'. Fill with '->ContentFileLength' bytes from '->ContentFilePtr'. Return to processing at '->NextFunction' (providing there was no problem!) */ PutWriteFileOpen (REQUEST_STRUCT *rqptr) { int status, EraseCount, PutRFM, TotalCount; char *cptr, *sptr, *zptr, *ContentTypePtr; char FileName [256]; CONTENT_TYPE ContType; BODY_PROCESS *prptr; PUT_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutWriteFileOpen()"); tkptr = rqptr->PutTaskPtr; prptr = rqptr->rqBody.ProcessPtr; if (rqptr->rqBody.ProcessedAs == BODY_PROCESSED_AS_MULTIPART_FORMDATA) { if (!prptr) { /* must have had the body processed before we can store it! */ ErrorInternal (rqptr, 0, ErrorSanityCheck, FI_LI); PutEnd (rqptr); return (SS$_BUGCHECK); } if (!prptr->MultipartFileName[0]) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PUT_MULTIPART_FILENAME), FI_LI); PutEnd (rqptr); return (SS$_BADPARAM); } if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "!&Z !&Z !&Z !&B", rqptr->ParseOds.NamDevicePtr, prptr->MultipartUploadFileName, prptr->MultipartFileName, prptr->PreviewOnly); } else { if (!rqptr->ParseOds.NamNameLength && !rqptr->ParseOds.NamTypeLength) { /* must have the parsed file name */ ErrorInternal (rqptr, 0, ErrorSanityCheck, FI_LI); PutEnd (rqptr); return (SS$_BUGCHECK); } if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "!&Z", rqptr->ParseOds.NamDevicePtr); } if (rqptr->ParseOds.NamTypeLength) cptr = rqptr->ParseOds.NamTypePtr; else if (prptr && prptr->MultipartUploadFileName[0]) { /* try to conjure up a fit-for-purpose file type */ for (cptr = prptr->MultipartUploadFileName; *cptr; cptr++); while (*cptr != '.' && cptr > prptr->MultipartUploadFileName) cptr--; if (*cptr != '.') { for (cptr = prptr->MultipartFileName; *cptr; cptr++); while (*cptr != '.' && cptr > prptr->MultipartFileName) cptr--; if (*cptr != '.') cptr = ""; } } else cptr = ""; if (rqptr->rqHeader.ContentTypePtr) { tkptr->FileContentTypePtr = rqptr->rqHeader.ContentTypePtr; ConfigContentType (&ContType, cptr); } else { /* If no MIME content-type is supplied with the request, as commonly occurs with Microsoft's WevDAV redirector, then attempt to come up with a plausable content-type from the file type (extension). This is useful information when a little later setting record characteristics on the file. */ tkptr->FileContentTypePtr = ConfigContentType (&ContType, cptr); } zptr = (sptr = tkptr->FileName) + sizeof(tkptr->FileName); /* use directory from path and form field 'uploadfilename' */ for (cptr = rqptr->ParseOds.NamDevicePtr; cptr < rqptr->ParseOds.NamNamePtr && sptr < zptr; *sptr++ = *cptr++); if (prptr && (prptr->MultipartFileName[0] || prptr->MultipartUploadFileName[0])) { if (!*(cptr = prptr->MultipartUploadFileName)) { cptr = prptr->MultipartFileName; while (*cptr) cptr++; while (cptr > prptr->MultipartFileName && *cptr != '/' && *cptr != '\\' && *cptr != ']') cptr--; if (*cptr == '/' || *cptr == '\\' || *cptr == ']') cptr++; } MapOdsUrlToVms (cptr, FileName, sizeof(FileName), 0, rqptr->rqPathSet.MapEllipsis, rqptr->PathOds); for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = *cptr++); /* update the content-type with whatever is specified in the part */ tkptr->FileContentTypePtr = prptr->MultipartContentTypePtr; } else if (prptr && (tkptr->PreviewOnly = prptr->PreviewOnly)) { /* use request total count to add a *unique* value to the file name */ InstanceMutexLock (INSTANCE_MUTEX_HTTPD); TotalCount = AccountingPtr->ProcessingTotalCount[HTTP12]; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); /* file name to indicate it's to be deleted on request rundown */ FaoToBuffer (FileName, sizeof(FileName), NULL, "-!4ZL!2ZL!2ZL!2ZL!2ZL!2ZL!2ZL!4ZL-", rqptr->rqTime.BeginTime7[0], rqptr->rqTime.BeginTime7[1], rqptr->rqTime.BeginTime7[2], rqptr->rqTime.BeginTime7[3], rqptr->rqTime.BeginTime7[4], rqptr->rqTime.BeginTime7[5], rqptr->rqTime.BeginTime7[6], TotalCount % 1000); for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = *cptr++); /* append the request file type to it */ for (cptr = rqptr->ParseOds.NamTypePtr; cptr < rqptr->ParseOds.NamVersionPtr && sptr < zptr; *sptr++ = *cptr++); } else { /* use the file name and type derived from the request path */ for (cptr = rqptr->ParseOds.NamNamePtr; cptr < rqptr->ParseOds.NamVersionPtr && sptr < zptr; *sptr++ = *cptr++); } if (sptr >= zptr) { ErrorGeneralOverflow (rqptr, FI_LI); PutEnd (rqptr); return (SS$_RESULTOVF); } *sptr = '\0'; tkptr->FileNameLength = sptr - tkptr->FileName; if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "!AZ", tkptr->FileName); tkptr->FileOpen = false; tkptr->FileOds.Fab = cc$rms_fab; tkptr->FileOds.Fab.fab$b_fac = FAB$M_PUT | FAB$M_BIO | FAB$M_TRN; tkptr->FileOds.Fab.fab$l_fop = FAB$M_SQO | FAB$M_TEF; /* pre-allocate space if the final size is fixed */ if (tkptr->ContentLengthFixed) { tkptr->FileOds.Fab.fab$l_alq = (rqptr->rqBody.ContentLength64 >> 9) + 1; if (rqptr->WebDavTaskPtr && rqptr->WebDavTaskPtr->MicrosoftAgent) { /* Microsoft's file creation sequence appears to be to PUT zero bytes, then to PROPPATCH some win32 attributes, and then finally PUT lotsa bytes; as three independent requests. Propagate the allocation quantity so that the originally created zero-length file gets extended to the full size required upon the first write. */ tkptr->FileOds.Fab.fab$w_deq = (short)tkptr->FileOds.Fab.fab$l_alq; } } /* if it's a WebDAV PUT then confine to a single version */ if (rqptr->WebDavTaskPtr) tkptr->FileOds.Fab.fab$l_fop |= FAB$M_CIF; tkptr->FileOds.Fab.fab$l_nam = &tkptr->FileOds.Nam; tkptr->FileOds.Fab.fab$b_rat = 0; if (prptr && prptr->UrlEncodedFabRfm && prptr->UrlEncodedFabRat) { tkptr->FileOds.Fab.fab$b_rfm = prptr->UrlEncodedFabRfm; tkptr->FileOds.Fab.fab$b_rat = prptr->UrlEncodedFabRat; if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "RFM:0x!2XL RAT:0x!2XL", tkptr->FileOds.Fab.fab$b_rfm, tkptr->FileOds.Fab.fab$b_rat); } else /* the [AddType] RFM will always override identification as text */ if (!ContType.PutRFM && ConfigSameContentType (tkptr->FileContentTypePtr, "text/", 5)) { /* "textual" content-type, guess about the _best_ RMS record format! */ int CrLfCount, LfCount; CrLfCount = LfCount = 0; if (rqptr->rqBody.DataCount > 1024) zptr = rqptr->rqBody.DataPtr + 1024; else zptr = rqptr->rqBody.DataPtr + rqptr->rqBody.DataCount; for (cptr = rqptr->rqBody.DataPtr; cptr < zptr; cptr++) { if (SAME2(cptr,'\r\n')) { CrLfCount++; cptr++; } else if (*cptr == '\n') LfCount++; } if (CrLfCount >= LfCount) { /* more CR+LFs than just LFs (DOS-style), STREAM */ tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STM; tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR; if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "RFM: stream (DOS)"); } else { /* STREAM-LF (Unix-style) */ tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STMLF; tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR; if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "RFM: stream-LF"); } tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR; } else /* the [AddType] RFM will always override identification as text */ if (!ContType.PutRFM && ConfigSameContentType (tkptr->FileContentTypePtr, "application/x-www-form-urlencoded", -1)) { /* STREAM-LF (Unix-style) */ tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STMLF; tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR; if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "RFM: stream-LF"); } else { /* binary - precedence: SET path then [AddType] then global config */ if (!(PutRFM = rqptr->rqPathSet.PutRFM)) if (!(PutRFM = ContType.PutRFM)) PutRFM = Config.cfMisc.PutBinaryRFM; if (PutRFM == PUT_RFM_FIX512) { tkptr->FileOds.Fab.fab$b_rfm = FAB$C_FIX; tkptr->FileOds.Fab.fab$w_mrs = 512; tkptr->FileOds.Fab.fab$b_rat = 0; cptr = "fixed-512"; } else if (PutRFM == PUT_RFM_STM) { tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STM; tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR; cptr = "stream"; } else if (PutRFM == PUT_RFM_STMCR) { tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STMCR; tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR; cptr = "stream-CR"; } else if (PutRFM == PUT_RFM_STMLF) { tkptr->FileOds.Fab.fab$b_rfm = FAB$C_STMLF; tkptr->FileOds.Fab.fab$b_rat = FAB$M_CR; cptr = "stream-LF"; } else { tkptr->FileOds.Fab.fab$b_rfm = FAB$C_UDF; tkptr->FileOds.Fab.fab$b_rat = 0; cptr = "undefined"; } if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "RFM: !AZ (non-text)", cptr); } tkptr->FileOds.Fab.fab$b_shr = FAB$M_NIL; #ifdef ODS_EXTENDED if (OdsExtended) { tkptr->FileOds.Fab.fab$l_fna = -1; tkptr->FileOds.Fab.fab$b_fns = 0; tkptr->FileOds.Fab.fab$l_nam = &tkptr->FileOds.Naml; tkptr->FileOds.NamlInUse = true; ENAMEL_RMS_NAML(tkptr->FileOds.Naml) tkptr->FileOds.Naml.naml$l_long_filename = tkptr->FileName; tkptr->FileOds.Naml.naml$l_long_filename_size = tkptr->FileNameLength; tkptr->FileOds.Naml.naml$l_filesys_name = tkptr->FileOds.SysFileName; tkptr->FileOds.Naml.naml$l_filesys_name_alloc = sizeof(tkptr->FileOds.SysFileName)-1; tkptr->FileOds.Naml.naml$l_long_expand = tkptr->FileOds.ExpFileName; tkptr->FileOds.Naml.naml$l_long_expand_alloc = sizeof(tkptr->FileOds.ExpFileName)-1; tkptr->FileOds.Naml.naml$l_long_result = tkptr->FileOds.ResFileName; tkptr->FileOds.Naml.naml$l_long_result_alloc = sizeof(tkptr->FileOds.ResFileName)-1; } else #endif /* ODS_EXTENDED */ { tkptr->FileOds.Fab.fab$l_fna = tkptr->FileName; tkptr->FileOds.Fab.fab$b_fns = tkptr->FileNameLength; tkptr->FileOds.Fab.fab$l_nam = &tkptr->FileOds.Nam; tkptr->FileOds.NamlInUse = false; tkptr->FileOds.Nam = cc$rms_nam; tkptr->FileOds.Nam.nam$l_esa = tkptr->FileOds.ExpFileName; tkptr->FileOds.Nam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH; tkptr->FileOds.Nam.nam$l_rsa = tkptr->FileOds.ResFileName; tkptr->FileOds.Nam.nam$b_rss = ODS2_MAX_FILE_NAME_LENGTH; } if (rqptr->WebDavTaskPtr) { /* propagate the profile security */ AuthAccessEnable (rqptr, tkptr->FileName, AUTH_ACCESS_WRITE); } else { if (prptr && isxdigit(prptr->ProtectionHexString[0])) tkptr->ProtectionMask = strtol (prptr->ProtectionHexString, NULL, 16); if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "0x!4XL", tkptr->ProtectionMask); /* initialize the protection extended attribute block */ tkptr->FileOds.Fab.fab$l_xab = &tkptr->FileOds.XabPro; tkptr->FileOds.XabPro = cc$rms_xabpro; tkptr->FileOds.XabPro.xab$w_pro = tkptr->ProtectionMask; /* use SYSPRV to ensure appropriate access/ownership */ sys$setprv (1, &SysPrvMask, 0, 0); } tkptr->FileOds.Fab.fab$l_ctx = &tkptr->FileOds; if (VMSok (status = sys$parse (&tkptr->FileOds.Fab, 0, 0))) OdsNamBlockAst (&tkptr->FileOds.Fab); /* override any (block I/O) incompatible existing file characteristics */ if (VMSok (status = OdsFileAcpInfo (&tkptr->FileOds, NULL, rqptr))) { if ((tkptr->FileOds.FileQio.RecAttr.fat$b_rtype != tkptr->FileOds.Fab.fab$b_rfm) || (tkptr->FileOds.FileQio.RecAttr.fat$b_rattrib != tkptr->FileOds.Fab.fab$b_rat)) { if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "INCOMPATIBLE existing!AZ!AZ modified", (tkptr->FileOds.FileQio.RecAttr.fat$b_rtype != tkptr->FileOds.Fab.fab$b_rfm) ? " RFM" : "", (tkptr->FileOds.FileQio.RecAttr.fat$b_rattrib, tkptr->FileOds.Fab.fab$b_rat) ? " RAT" : ""); for (EraseCount = 0; VMSok (status = sys$erase (&tkptr->FileOds.Fab, 0, 0)); EraseCount++); if (status == RMS$_FNF && EraseCount) status = SS$_NORMAL; if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "sys$erase() !&S", status); } } if (VMSok (status) || status == SS$_NOSUCHFILE) status = sys$create (&tkptr->FileOds.Fab, 0, 0); if (rqptr->WebDavTaskPtr) AuthAccessEnable (rqptr, 0, 0); else sys$setprv (0, &SysPrvMask, 0, 0); if (VMSnok (status) && tkptr->FileOds.Fab.fab$l_stv) status = tkptr->FileOds.Fab.fab$l_stv; if (VMSnok (status)) { /* sys$create() error */ if (rqptr->WebDavTaskPtr) if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PUT create !&S !AZ", status, tkptr->FileOds.ExpFileName); rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileName; ErrorVmsStatus (rqptr, status, FI_LI); PutWriteFileClose (rqptr); PutEnd (rqptr); return (status); } tkptr->FileOpen = true; tkptr->SysCreateStatus = status; /* set up the generic information from the NAM(L) block */ tkptr->FileOds.Fab.fab$l_ctx = &tkptr->FileOds; OdsNamBlockAst (&tkptr->FileOds.Fab); tkptr->FileOds.Rab = cc$rms_rab; tkptr->FileOds.Rab.rab$l_fab = &tkptr->FileOds.Fab; tkptr->FileOds.Rab.rab$l_ctx = rqptr; tkptr->FileOds.Rab.rab$l_rop = RAB$M_BIO | RAB$M_ASY | RAB$M_TPT; if (VMSnok (status = sys$connect (&tkptr->FileOds.Rab, 0, 0))) { rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileName; ErrorVmsStatus (rqptr, status, FI_LI); PutWriteFileClose (rqptr); PutEnd (rqptr); } return (status); } /*****************************************************************************/ /* Called when the file has been completely written or an error has been detected. The presence of an error message results in the file being deleted. If OK the file attributes are changed to limit versions and to stream-LF is "textual". There is a small window between closing the file and changing the attributes where it could conceivably be opened for write by another process and interfere with that change. Not a problem within this one server because all this processing is occuring at user-AST-delivery level. Don't know what to do about it for the moment so I'll just say it's a low-risk scenario and live with it for now! */ PutWriteFileClose (REQUEST_STRUCT *rqptr) { static int SingleVersion = 1; int status; char *cptr, *sptr, *tptr; char ProtectionString [32]; PUT_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutWriteFileClose()"); tkptr = rqptr->PutTaskPtr; if (!tkptr->FileOpen) return; tkptr->FileOpen = false; if (tkptr->FileOds.Fab.fab$w_ifi) sys$close (&tkptr->FileOds.Fab, 0, 0); if (ERROR_REPORTED (rqptr)) { /*********************************************/ /* an error has occured, delete created file */ /*********************************************/ /* use SYSPRV to ensure erasure of file */ sys$setprv (1, &SysPrvMask, 0, 0); tkptr->FileOds.Fab.fab$l_fop = FAB$M_NAM; status = sys$erase (&tkptr->FileOds.Fab, 0, 0); if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "sys$erase() !&S", status); sys$setprv (0, &SysPrvMask, 0, 0); return; } else { if (rqptr->WebDavTaskPtr) { /**************/ /* WebDAV PUT */ /**************/ if (tkptr->SysCreateStatus == RMS$_CREATED) { DavWebHref (rqptr, rqptr->ParseOds.ExpFileName, 0); DavWebResponse201 (rqptr); } else DavWebResponse (rqptr, 204, 0, "existing, overwrite", FI_LI); return; } /**************/ /* non-WebDAV */ /**************/ if (Config.cfMisc.PutVersionLimit) { /* use SYSPRV to ensure modification of file */ sys$setprv (1, &SysPrvMask, 0, 0); status = OdsFileAcpModify (&tkptr->FileOds, NULL, &Config.cfMisc.PutVersionLimit, NULL, rqptr); sys$setprv (0, &SysPrvMask, 0, 0); if (VMSnok (status)) { /* as an error has occured terminate the PUT processing */ rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = tkptr->FileName; ErrorVmsStatus (rqptr, status, FI_LI); return; } } if (tkptr->PreviewOnly) { /****************/ /* preview only */ /****************/ /* redirect to temporary file */ int size; cptr = MapVmsPath (tkptr->FileName, rqptr); size = strlen(cptr) + 256; sptr = ResponseLocation (rqptr, NULL, size); /* the leading space indicates that it's a change of HTTP method */ FaoToBuffer (sptr, size, NULL, " GET !&%AZ", cptr); return; } if (rqptr->rqResponse.LocationPtr) { /********************/ /* success redirect */ /********************/ return; } if (ConfigSameContentType (tkptr->FileContentTypePtr, "text/", 5) || ConfigSameContentType (tkptr->FileContentTypePtr, "application/x-www-form-urlencoded", -1)) tptr = MsgFor(rqptr,MSG_GENERAL_DOCUMENT); else tptr = MsgFor(rqptr,MSG_GENERAL_FILE); FormatProtection (tkptr->ProtectionMask, ProtectionString); cptr = MapVmsPath (tkptr->FileName, rqptr); /* ReportSuccess() will convert this 201 back to a 200 */ rqptr->rqResponse.HttpStatus = 201; rqptr->rqResponse.PreExpired = PRE_EXPIRE_PUT; ReportSuccess (rqptr, "!AZ  !&;AZ  !AZ (!UL bytes)  (!AZ)", tptr, cptr, cptr, tkptr->FileOds.Nam_fnb & NAM$M_LOWVER ? MsgFor(rqptr,MSG_PUT_SUPERCEDED) : MsgFor(rqptr,MSG_PUT_CREATED), tkptr->FileSizeBytes, ProtectionString); return; } } /*****************************************************************************/ /* Create a directory! */ int PutCreateDirectory (REQUEST_STRUCT *rqptr) { static unsigned short ProtectionEnable = 0xffff, /* alter all S,O,G,W */ EnsureSystemAccessMask = 0xfff0; /* S:RWED */ static $DESCRIPTOR (DirectoryDsc, ""); int status; unsigned short Length; char *dptr; PUT_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutCreateDirectory()"); tkptr = rqptr->PutTaskPtr; dptr = rqptr->ParseOds.NamDevicePtr; DirectoryDsc.dsc$a_pointer = dptr; DirectoryDsc.dsc$w_length = strlen(dptr); tkptr->ProtectionMask &= EnsureSystemAccessMask; AuthAccessEnable (rqptr, DirectoryDsc.dsc$a_pointer, AUTH_ACCESS_WRITE); status = lib$create_dir (&DirectoryDsc, 0, &ProtectionEnable, &tkptr->ProtectionMask, 0, 0); if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "lib$create_dir() !&S", status); AuthAccessEnable (rqptr, 0, 0); if (status == SS$_CREATED) { /* ReportSuccess() will convert this 201 back to a 200 */ rqptr->rqResponse.HttpStatus = 201; rqptr->rqResponse.PreExpired = PRE_EXPIRE_PUT; ReportSuccess (rqptr, "!AZ  !&;&_AZ  !AZ", MsgFor(rqptr,MSG_GENERAL_DIRECTORY), rqptr->rqHeader.PathInfoPtr, rqptr->rqHeader.PathInfoPtr, MsgFor(rqptr,MSG_PUT_CREATED)); } else if (status == SS$_NORMAL) { rqptr->rqResponse.HttpStatus = 409; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PUT_DIR_EXISTS), FI_LI); } else { rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = dptr; ErrorVmsStatus (rqptr, status, FI_LI); } return (status); } /*****************************************************************************/ /* Deletes both files and directories. */ PutDelete (REQUEST_STRUCT *rqptr) { BOOL DeletingDirectory; int status, EraseCount, FileCount, DirFileNameLength; char *cptr, *sptr, *zptr; char DirFileName [ODS_MAX_FILE_NAME_LENGTH+1], SearchFileName [ODS_MAX_FILE_NAME_LENGTH+1]; PUT_TASK *tkptr; ODS_STRUCT DeleteOds, SearchOds; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "PutDelete()"); tkptr = rqptr->PutTaskPtr; /* bit of a shonky, remove trailing ';*' from path */ cptr = rqptr->rqHeader.PathInfoPtr + rqptr->rqHeader.PathInfoLength; if (*(cptr-1) == '*') cptr--; if (*(cptr-1) == ';') cptr--; *cptr = '\0'; rqptr->rqHeader.PathInfoLength = cptr - rqptr->rqHeader.PathInfoPtr; DeletingDirectory = !rqptr->ParseOds.NamNameLength && !rqptr->ParseOds.NamTypeLength; if (DeletingDirectory) { OdsNameOfDirectoryFile (rqptr->ParseOds.ExpFileName, rqptr->ParseOds.ExpFileNameLength, DirFileName, &DirFileNameLength); AuthAccessEnable (rqptr, DirFileName, AUTH_ACCESS_READ); OdsStructInit (&SearchOds, true); OdsParse (&SearchOds, DirFileName, DirFileNameLength, NULL, 0, 0, NULL, rqptr); AuthAccessEnable (rqptr, 0, 0); } else { AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_READ); OdsParse (&SearchOds, rqptr->ParseOds.ExpFileName, rqptr->ParseOds.ExpFileNameLength, ";*", 2, 0, NULL, rqptr); AuthAccessEnable (rqptr, 0, 0); } if (VMSnok (status = SearchOds.Fab.fab$l_sts)) { rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.ExpFileName; ErrorVmsStatus (rqptr, status, FI_LI); return (status); } /*******************/ /* delete the file */ /*******************/ FileCount = 0; for (;;) { /* SYSPRV to ensure search (provided protection is S:RWED) */ sys$setprv (1, &SysPrvMask, 0, 0); status = OdsSearch (&SearchOds, NULL, rqptr); sys$setprv (0, &SysPrvMask, 0, 0); if (VMSnok (status)) break; FileCount++; OdsStructInit (&DeleteOds, true); OdsParse (&DeleteOds, SearchOds.ResFileName, SearchOds.ResFileNameLength, NULL, 0, NAM$M_SYNCHK, NULL, rqptr); if (VMSnok (status = SearchOds.Fab.fab$l_sts)) { rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = SearchOds.ExpFileName; ErrorVmsStatus (rqptr, status, FI_LI); return (status); } DeleteOds.NamVersionPtr[0] = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "!&Z", DeleteOds.ExpFileName); if (rqptr->WebDavTaskPtr) AuthAccessEnable (rqptr, DeleteOds.ExpFileName, AUTH_ACCESS_WRITE); else /* use SYSPRV to ensure appropriate access/ownership */ sys$setprv (1, &SysPrvMask, 0, 0); EraseCount = 0; while (VMSok (status = sys$erase (&DeleteOds.Fab, 0, 0))) { EraseCount++; if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "!UL sys$erase() !&S", EraseCount, status); } if (status == RMS$_FNF && EraseCount) status = SS$_NORMAL; if (rqptr->WebDavTaskPtr) AuthAccessEnable (rqptr, 0, 0); else sys$setprv (0, &SysPrvMask, 0, 0); if (VMSnok (status) && DeleteOds.Fab.fab$l_stv) status = DeleteOds.Fab.fab$l_stv; OdsParseRelease (&DeleteOds); if (VMSnok (status)) { rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = SearchOds.ExpFileName; ErrorVmsStatus (rqptr, status, FI_LI); return (status); } } if (status == RMS$_NMF) status = SS$_NORMAL; OdsParseRelease (&SearchOds); if (VMSok (status)) { rqptr->rqResponse.PreExpired = PRE_EXPIRE_PUT; if (DeletingDirectory) sptr = MsgFor(rqptr,MSG_GENERAL_DIRECTORY); else sptr = MsgFor(rqptr,MSG_GENERAL_FILE); ReportSuccess (rqptr, "!AZ  !&;&_AZ  !AZ", sptr, rqptr->rqHeader.PathInfoPtr, MsgFor(rqptr,MSG_PUT_DELETED)); return (status); } else { if (DeletingDirectory && status == RMS$_FNF) status = RMS$_DNF; rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = SearchOds.ExpFileName; ErrorVmsStatus (rqptr, status, FI_LI); return (status); } } /*****************************************************************************/