/*****************************************************************************/ /* Redirect.c The 'rqptr->rqResponse.LocationPtr' is non-NULL indicating redirection. This can be local, indicated by a leading '/', or it can be non-local, indicated by anything other than a leading '/', usually a full URL including scheme (e.g. "http:"). If the URL comprises something like "///some/path/or/other" then the request scheme ("http:", "https:") and "Host:" or service host:port is substituted in the appropriate position in the URL. If "//:port/path" (i.e. begins with "//:") the request scheme and host are substituted into the URL and it becomes effectively a redirect to a different port on the same host. If "//host.domain/path/" then just the request scheme is added. If "http:///path/" or "https:///path/" then the service host:port is added to the redirection URL (essentially this provides for a change of request scheme). If the 'rqptr->rqResponse.LocationPtr' has as it's last character a '?' and no query string itself, and if the request contains a query string then that is appended to the redirection URL when building the redirected request. Special case redirect; relies on being able to manipulate host record in the DNS or local name resolution database. If a "*.the.proxy.host" DNS (CNAME) record is resolved it allows any host name ending in ".the.proxy.host" to resolve to the corresponding IP address. Similarly (at least the Compaq TCP/IP Services) local host database allows an alias like "another.host.name.proxy.host.name" for the proxy host name. Both of these would allow a browser to access "another.host.name.proxy.host.name" with it resolved to the proxy service. The request "Host:" field would contain "another.host.name.proxy.host.name". This is specially searched for, detected and processed by this function. See description in PROXY.C for more detail on usage. If authorization is enabled then all redirected requests should be returned to the client in case authorization information needs to be applied to the new request. This may involve reformating a local redirection into a non-local one. If authentication is not enabled then check both local and non-local redirections to see if it can be handled locally. Return normal status to indicate local-redirection (and that the thread should NOT be disposed of), or an error status to indicate client-handled, non-local-redirection, or a legitimate error (and that the thread can go). VERSION HISTORY --------------- 21-MAR-2017 MGD bugfix; use rqHeader.RequestBody.. for body with header 28-DEC-2015 MGD move from REQUEST.C to indepedent module */ /*****************************************************************************/ #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 #include /* application header files */ #include "wasd.h" #define WASD_MODULE "REDIRECT" /******************/ /* global storage */ /******************/ /********************/ /* external storage */ /********************/ #ifdef DBUG extern BOOL Debug; #else #define Debug 0 #endif extern BOOL Http2Enabled; extern int ToLowerCase[], ToUpperCase[]; extern char ErrorSanityCheck[], SoftwareID[]; #define acptr AccountingPtr extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern MSG_STRUCT Msgs; extern WATCH_STRUCT Watch; /****************************************************************************/ /* */ int RedirectRequest (REQUEST_STRUCT *rqptr) { #define STRCAT(string) { \ for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++); \ } #define CHRCAT(ch) { \ if (sptr < zptr) *sptr++ = ch; \ } static char *SchemeHttp = "/http://", *SchemeHttps = "/https://", *SchemeFtp = "/ftp://"; static BOOL RedirectWildcardChecked, RedirectWildcardEnabled; static char TextContentLength [32]; static $DESCRIPTOR (TextContentLengthDsc, TextContentLength); static $DESCRIPTOR (TextContentLengthFaoDsc, "Content-Length: !UL\r\n\0"); BOOL IncludeQueryString, ConcatQueryString, LocalRedirection; int idx, length, status, AlphaCount, BodyCount, ExciseCount, HostDotCount, PortNumber, ReadBufferSize, RequestHeaderLength, RequestLineLength, SchemeLength, ServiceDotCount, StringDotCount, UrlLength; ulong BytesRx [QUAD2]; char *aptr, *cptr, *sptr, *zptr, *BodyPtr, *HttpMethodNamePtr, *ReadBufferPtr, *RequestLinePtr, *PathPtr, *SchemePtr, *UrlPtr, *UrlEndPtr; char HttpMethodName [32], HostField [128+16], /* allow for agents with silly amounts of request header */ RedirectionUrl [16384]; DICT_STRUCT *dicptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RedirectRequest() !&Z", rqptr->rqResponse.LocationPtr); /* at this stage just to delineate this wildcard annoyance */ #define REDIRECT_WILDCARD 1 #if REDIRECT_WILDCARD if (!RedirectWildcardChecked) { /* bit of a kludge but "Acckkk Alex!" */ RedirectWildcardChecked = true; RedirectWildcardEnabled = (SysTrnLnm("WASD_REDIRECT_WILDCARD") != NULL); } #endif /* REDIRECT_WILDCARD */ if (rqptr->rqPathSet.Alert) RequestAlert (rqptr); if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "REDIRECT !AZ", rqptr->rqResponse.LocationPtr); if (rqptr->RedirectCount++ > REQUEST_REDIRECTION_MAX) { rqptr->rqResponse.LocationPtr = NULL; rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_REDIRECTION), FI_LI); return (STS$K_ERROR); } rqptr->rqResponse.HttpStatus = 302; /* A leading space is normally not possible. This indicates a specific HTTP method must be used. Format is: "METHOD". */ if (rqptr->rqResponse.LocationPtr[0] == ' ') { HttpMethodNamePtr = sptr = HttpMethodName; for (cptr = rqptr->rqResponse.LocationPtr + 1; *cptr && *cptr != ' '; *sptr++ = *cptr++); *sptr = '\0'; rqptr->rqResponse.LocationPtr = cptr + 1; } else HttpMethodNamePtr = rqptr->rqHeader.MethodName; LocalRedirection = true; for (cptr = rqptr->rqResponse.LocationPtr; *cptr && *cptr != '?'; cptr++) { if (*cptr != ':' && *cptr != '/') continue; if (*cptr == ':') LocalRedirection = false; while (*cptr && *cptr != '?') cptr++; break; } if (ConcatQueryString = ((*cptr == '?') && !SAME2(cptr,'?\0'))) while (*cptr && !SAME2(cptr,'?\0')) cptr++; if (IncludeQueryString = SAME2(cptr,'?\0')) { if (!rqptr->rqHeader.QueryStringLength || !rqptr->rqHeader.QueryStringPtr[0]) { IncludeQueryString = ConcatQueryString = false; *cptr = '\0'; } } if (ConcatQueryString) *cptr = '\0'; if (LocalRedirection) UrlLength = MapUrl_VirtualPath (rqptr->rqHeader.PathInfoPtr, rqptr->rqResponse.LocationPtr, RedirectionUrl, sizeof(RedirectionUrl)); else UrlLength = strzcpy (RedirectionUrl, rqptr->rqResponse.LocationPtr, sizeof(RedirectionUrl)); if (UrlLength > sizeof(RedirectionUrl)-1) UrlLength = -1; if (UrlLength < 0) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); ErrorGeneralOverflow (rqptr, FI_LI); return (STS$K_ERROR); } /* now ignore old value of location redirection pointer */ rqptr->rqResponse.LocationPtr = NULL; if (RedirectionUrl[0] == '/' && RedirectionUrl[1] != '/') LocalRedirection = true; else LocalRedirection = false; if (LocalRedirection) { /*********************/ /* local redirection */ /*********************/ InstanceGblSecIncrLong (&acptr->RedirectLocalCount); /* create a dictionary to contain the rebuilt request */ dicptr = DictCreate (rqptr, -1); if (!rqptr->Http2Stream.Http2Ptr && rqptr->rqHeader.ContentLength) { /* allow for any content received along with the header */ PUT_QUAD_LONG (rqptr->BytesRx, BodyCount); BodyCount -= rqptr->rqHeader.RequestHeaderLength; sptr = rqptr->rqHeader.RequestHeaderPtr + rqptr->rqHeader.RequestHeaderLength; if (BodyCount) { BodyPtr = VmGetHeap (rqptr, BodyCount); memcpy (BodyPtr, sptr, BodyCount); } } else BodyCount = 0; HostField[0] = '\0'; if (RedirectionUrl[0] == '/' && (MATCH8 (RedirectionUrl, SchemePtr = SchemeHttp) || MATCH9 (RedirectionUrl, SchemePtr = SchemeHttps) || MATCH7 (RedirectionUrl, SchemePtr = SchemeFtp))) { /************/ /* to proxy */ /************/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "!&Z", RedirectionUrl); if (SchemePtr == SchemeHttp) SchemeLength = 8; else if (SchemePtr == SchemeHttps) SchemeLength = 9; else SchemeLength = 7; if (!rqptr->rqHeader.HostPtr) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_GATEWAY), FI_LI); return (STS$K_ERROR); } /* If the path has been SET 'proxy=reverse=location=' then this and the original host information is conveyed to the redirected proxy request using redirect-persistent storage made available specifically for this purpose. */ if (rqptr->rqPathSet.ProxyReverseLocationPtr) { length = strlen(rqptr->ServicePtr->RequestSchemeNamePtr) + strlen(rqptr->rqHeader.HostPtr) + strlen(rqptr->rqPathSet.ProxyReverseLocationPtr) + 3; rqptr->ProxyReverseLocationPtr = VmGetHeap (rqptr, length); zptr = (sptr = rqptr->ProxyReverseLocationPtr) + length; STRCAT (rqptr->ServicePtr->RequestSchemeNamePtr) STRCAT ("//") STRCAT (rqptr->rqHeader.HostPtr) STRCAT (rqptr->rqPathSet.ProxyReverseLocationPtr) *sptr = '\0'; } #if REDIRECT_WILDCARD if (RedirectWildcardEnabled) { /* Check for a DNS 'wildcard' leading the service name. That is the "Host:" field contains more domain name components than the proxy service domain name. Do this by counting the number of periods in the strings. Acckkk Alex! Never been quite convinced of the need for this ;^) */ AlphaCount = HostDotCount = ServiceDotCount = 0; for (cptr = rqptr->rqHeader.HostPtr; *cptr; cptr++) { if (isalpha(*cptr)) AlphaCount++; if (*cptr != '.') continue; HostDotCount++; } if (AlphaCount) { /* only bother doing this if it's not dotted-decimal */ for (cptr = rqptr->ServicePtr->ServerHostPort; *cptr; cptr++) { if (*cptr != '.') continue; ServiceDotCount++; } } if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("!UL !UL !&Z !UL !&Z\n", AlphaCount, HostDotCount, rqptr->rqHeader.HostPtr, ServiceDotCount, rqptr->ServicePtr->ServerHostPort); } #endif /* REDIRECT_WILDCARD */ #if REDIRECT_WILDCARD if (RedirectWildcardEnabled && AlphaCount && HostDotCount > ServiceDotCount) { /**********************/ /* wildcard DNS proxy */ /**********************/ /* At this stage the "Host:" field will look something like 'the.host.name.the.proxy.service:port' and the redirection URL '/https://the.proxy.service/http://the.host.name.the.proxy.service:port/path' We need to turn this into a proxy redirection looking like 'https://the.host.name/path'. */ sptr = RedirectionUrl + SchemeLength; if (*sptr && *sptr != '/') { /* count the number of periods 'the.proxy.service/' component */ StringDotCount = 0; for (cptr = sptr; *cptr && *cptr != '/'; cptr++) { if (*cptr != '.') continue; StringDotCount++; } /* possible but not desirable */ if (StringDotCount >= HostDotCount) StringDotCount = HostDotCount; /* find the start of the redirection URL path */ if (*cptr) cptr++; while (*cptr && *cptr != '/') cptr++; PathPtr = cptr; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("!UL !&Z !UL !&Z\n", HostDotCount, rqptr->rqHeader.HostPtr, StringDotCount, PathPtr); } else { StringDotCount = ServiceDotCount; PathPtr = RedirectionUrl + SchemeLength; } /* recreate the "Host:" field */ zptr = (sptr = HostField) + sizeof(HostField)-1; cptr = rqptr->rqHeader.HostPtr; while (HostDotCount-- > StringDotCount) { if (*cptr == '.') { if (sptr < zptr) *sptr++ = *cptr; cptr++; } while (*cptr && *cptr != '.') { if (sptr < zptr) *sptr++ = *cptr; cptr++; } } *sptr-- = '\0'; /* check if the last 'name' component was actually a 'port' */ while (sptr > HostField && isdigit(*sptr)) sptr--; /* if so change the period to a colon */ if (sptr > HostField && *sptr == '.') *sptr = ':'; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", HostField); /* reserve space in redirection dictionary */ length = UrlLength + 256; denptr = DictInsert (dicptr, DICT_TYPE_INTERNAL, "request_line", 12, NULL, length); /* build request line directly in dictionary entry */ RequestLinePtr = DICT_GET_VALUE(denptr); zptr = (sptr = RequestLinePtr) + length; STRCAT (HttpMethodNamePtr) CHRCAT (' ') STRCAT (SchemePtr) STRCAT (HostField) STRCAT (PathPtr) } else #endif /* REDIRECT_WILDCARD */ { /**********************/ /* one-shot DNS proxy */ /**********************/ /* recreate the "Host:" field */ zptr = (sptr = HostField) + sizeof(HostField)-1; cptr = RedirectionUrl + SchemeLength; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", HostField); /* reserve space in redirection dictionary */ length = UrlLength + 256; denptr = DictInsert (dicptr, DICT_TYPE_INTERNAL, "request_line", 12, NULL, length); /* build request line directly in dictionary entry */ RequestLinePtr = DICT_GET_VALUE(denptr); zptr = (sptr = RequestLinePtr) + length; STRCAT (HttpMethodNamePtr) CHRCAT (' ') STRCAT (RedirectionUrl+1) } } else { /****************/ /* not to proxy */ /****************/ /* reserve space in redirection dictionary */ length = UrlLength + 256; denptr = DictInsert (dicptr, DICT_TYPE_INTERNAL, "request_line", 12, NULL, length); /* build request line directly in dictionary entry */ RequestLinePtr = DICT_GET_VALUE(denptr); zptr = (sptr = RequestLinePtr) + length; STRCAT (HttpMethodNamePtr) CHRCAT (' ') STRCAT (RedirectionUrl) } if (IncludeQueryString) { if (ConcatQueryString) CHRCAT ('&') STRCAT (rqptr->rqHeader.QueryStringPtr) } if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) STRCAT (" HTTP/1.1") else STRCAT (" HTTP/1.0") /* complete the "request_line" dictionary entry */ RequestLineLength = sptr - RequestLinePtr; DictValueLength (denptr, RequestLineLength); /* add (with some massage) the original request's header fields */ DictIterate (rqptr->rqDictPtr, NULL); while (denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_REQUEST)) { cptr = DICT_GET_KEY(denptr); if (TOUP(*cptr) == 'D' && strsame (cptr, "Destination:", 12)) { if (HostField[0]) { /* redirecting to proxy */ aptr = cptr + 12; while (*aptr && *aptr != '/') aptr++; if (*aptr && *(aptr+1) == '/') { aptr += 2; while (*aptr && *aptr != '/') aptr++; } /* reserve space in the dictionary then build value */ length = SchemeLength + strlen(HostField) + strlen(aptr); denptr = DictInsert (dicptr, DICT_TYPE_REQUEST, "destination", 11, NULL, length); zptr = (sptr = DICT_GET_VALUE (denptr)) + length; STRCAT (SchemePtr) STRCAT (HostField) STRCAT (aptr) continue; } } else if (TOUP(*cptr) == 'H' && strsame (cptr, "Host:", 5)) { if (HostField[0]) { /* redirecting to proxy */ DictInsert (dicptr, DICT_TYPE_REQUEST, "host", 4, HostField, -1); continue; } } /** TODO MGD 30-AUG-2005 **/ #if 0 else if (TOUP(*cptr) == 'R' && strsame (cptr, "Referer:", 8)) { /* too complex massaging "Referer:" when redirecting to proxy */ if (HostField[0]) continue; } #endif /* otherwise just include the field */ DictInsert (dicptr, DICT_TYPE_REQUEST, DICT_GET_KEY(denptr), DICT_GET_KEY_LEN(denptr), DICT_GET_VALUE(denptr), DICT_GET_VALUE_LEN(denptr)); } if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_0) if (rqptr->PersistentRequest) DictInsert (dicptr, DICT_TYPE_REQUEST, "connection", 10, "keep-alive", 10); /* if generated by a CGI "Location:" */ if (rqptr->rqCgi.HeaderLength) { /* parse CGI response fields into the dictionary */ zptr = (cptr = rqptr->rqCgi.HeaderPtr) + rqptr->rqCgi.HeaderLength; while (cptr < zptr) { int klen, vlen; char *kptr, *vptr; for (kptr = cptr; *cptr != ':' && cptr < zptr; cptr++); if (cptr >= zptr) break; klen = cptr - kptr; for (++cptr; ISLWS(*cptr) && cptr < zptr; cptr++); if (cptr >= zptr) break; for (vptr = cptr; NOTEOL(*cptr) && cptr < zptr; cptr++); if (cptr >= zptr) break; vlen = cptr - vptr; while (ISLWS(*cptr) && cptr < zptr) cptr++; DictInsert (dicptr, DICT_TYPE_REQUEST, kptr, klen, vptr, vlen); } } /*******************/ /* request restart */ /*******************/ if (WATCHING (rqptr, WATCH_INTERNAL)) { WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "REDIRECT dictionary before"); DictWatch (rqptr->rqDictPtr, NULL, "*"); } /* buffer anything that is necessary to retain after memset() */ PUT_QUAD_QUAD (rqptr->BytesRx, BytesRx); ReadBufferPtr = rqptr->rqNet.ReadBufferPtr; ReadBufferSize = rqptr->rqNet.ReadBufferSize; /* including any directory meta entries from current to new */ DictIterate (rqptr->rqDictPtr, NULL); while (denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_CONFIG)) { /* can only be done because the memory belongs to the same request */ DictExtractEntry (denptr); DictInsertEntry (dicptr, denptr); } /* and any notepad entry */ if (denptr = rqptr->NotePadDictEntry) { DictExtractEntry (denptr); DictInsertEntry (dicptr, denptr); } /* zero the portion of the request structure that is not persistent */ memset ((char*)&rqptr->ZeroedBegin, 0, (char*)&rqptr->ZeroedEnd - (char*)&rqptr->ZeroedBegin); /* re-timestamp the transaction */ sys$gettim (&rqptr->rqTime.BeginTime64); sys$numtim (&rqptr->rqTime.BeginNumTime, &rqptr->rqTime.BeginTime64); HttpGmTimeString (rqptr->rqTime.GmDateTime, &rqptr->rqTime.BeginTime64); /* initialize the timer for input */ HttpdTimerSet (rqptr, TIMER_INPUT, 0); /* if available then set any initial report to be via the script */ if (rqptr->ServicePtr->ErrorReportPath[0]) rqptr->rqResponse.ErrorReportByRedirect = true; /* restore buffered stuff */ PUT_QUAD_QUAD (BytesRx, rqptr->BytesRx); rqptr->rqNet.ReadBufferPtr = ReadBufferPtr; rqptr->rqNet.ReadBufferSize = ReadBufferSize; if (BodyCount) { /* restore body supplied with the request header */ memcpy (rqptr->rqNet.ReadBufferPtr, BodyPtr, BodyCount); rqptr->rqHeader.RequestBodyPtr = rqptr->rqNet.ReadBufferPtr; rqptr->rqHeader.RequestBodyCount = BodyCount; VmFreeFromHeap (rqptr, BodyPtr, FI_LI); rqptr->NetIoPtr->ReadCount = BodyCount; /* fudge the bytes received */ PUT_LONG_QUAD (BodyCount, rqptr->BytesRx); PUT_LONG_QUAD (BodyCount, rqptr->NetIoPtr->BytesRawRx); } /* destroy the current request dictionary */ DictDestroy (rqptr->rqDictPtr); /* use the redirect dictionary just built */ rqptr->rqDictPtr = dicptr; /* restore from when the entry was placed in the dictionary */ rqptr->rqHeader.RequestLinePtr = RequestLinePtr; rqptr->rqHeader.RequestLineLength = RequestLineLength; if (WATCHING (rqptr, WATCH_INTERNAL)) { WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "REDIRECT dictionary after"); DictWatch (rqptr->rqDictPtr, NULL, "*"); } /* normal status indicates its a local redirection */ return (SS$_NORMAL); } else { /*************************/ /* non-local redirection */ /*************************/ InstanceGblSecIncrLong (&acptr->RedirectRemoteCount); /* there will three occasions the URL is used */ length = (UrlLength * 3) + (256 * 3); aptr = VmGetHeap (rqptr, length); zptr = (sptr = aptr) + length; /* begin building the textual (body) redirection information */ STRCAT (HttpStatusCodeText(302)); STRCAT (" ServicePtr->RequestSchemeNamePtr) STRCAT ("//") if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) STRCAT (rqptr->rqHeader.HostPtr) else STRCAT (rqptr->ServicePtr->ServerHostPort) STRCAT (RedirectionUrl+2) } else if (MATCH3 (RedirectionUrl, "//:")) { /* request scheme and host needs to be supplied locally */ STRCAT (rqptr->ServicePtr->RequestSchemeNamePtr) STRCAT ("//") if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) cptr = rqptr->rqHeader.HostPtr; else cptr = rqptr->ServicePtr->ServerHostPort; while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++; STRCAT (RedirectionUrl+2) } else if (MATCH2 (RedirectionUrl, "//")) { /* request scheme needs to be supplied locally */ STRCAT (rqptr->ServicePtr->RequestSchemeNamePtr) STRCAT (RedirectionUrl) } else if (MATCH8 (RedirectionUrl, "http:///")) { /* host needs to be supplied locally */ STRCAT ("http://") if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) STRCAT (rqptr->rqHeader.HostPtr) else STRCAT (rqptr->ServicePtr->ServerHostPort); STRCAT (RedirectionUrl+7) } else if (MATCH0 (RedirectionUrl, "https:///", 9)) { /* host needs to be supplied locally */ STRCAT ("https://") if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) STRCAT (rqptr->rqHeader.HostPtr) else STRCAT (rqptr->ServicePtr->ServerHostPort) STRCAT (RedirectionUrl+8) } else { /* redirection contains full URL */ STRCAT (RedirectionUrl) } if (IncludeQueryString) { if (ConcatQueryString) CHRCAT ('&') STRCAT (rqptr->rqHeader.QueryStringPtr); } UrlEndPtr = sptr; /* note the end of the redirection URL */ STRCAT ("\">") for (cptr = UrlPtr; cptr < UrlEndPtr && sptr < zptr; *sptr++ = *cptr++); STRCAT ("\n") *sptr = '\0'; length = sptr - aptr; /* now (somewhat shonkily) use remaining buffer for location header */ sptr++; for (cptr = "Location:"; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = UrlPtr; cptr < UrlEndPtr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (sptr >= zptr) ErrorNoticed (rqptr, SS$_BUFFEROVF, ErrorSanityCheck, FI_LI); ResponseHeader (rqptr, 302, "text/html", length, NULL, aptr+length+1); /* return response to client */ NetWrite (rqptr, &RequestEnd, aptr, length); /* indicate it's non-local redirection */ return (SS$_NONLOCAL); } #undef STRCAT #undef CHRCAT } /*****************************************************************************/