/*++ Copyright (c) 1999 Microsoft Corporation Module Name : SSI.cool Abstract: This module implements the SSI handler to handle .stm, .shtm files Author: Anil Ruia ( AnilR ) 29-Nov-1999 Environment: COM+ - User Mode Managed Run Time Project: Web Server --*/ using System.ASP; using System.IO; using System.IIS.PrivateUtils; using System.IIS.CGI; using System.Interop; using System.Reflection.Emit; using System.Collections; namespace System.IIS { [sysstruct(pack=PackingSizeEnum.Size4, format=ClassFormat.Unicode)] internal class tm { public int tm_sec; public int tm_min; public int tm_hour; public int tm_mday; public int tm_mon; public int tm_year; public int tm_wday; public int tm_yday; public int tm_isdst; public tm(int sec, int min, int hour, int mday, int mon, int year) { tm_sec = sec; tm_min = min; tm_hour = hour; tm_mday = mday; tm_mon = mon; tm_year = year; } } internal class SSIBuffer { public byte[] data; public int position; public SSIBuffer(byte[] buf, int pos) { data = buf; position = pos; } } public class SSIHandler : IHttpHandler { private String _errMsg = "Error occured while parsing SSI document"; private String _timeFmt = "%A %B %#d %Y"; private String _sizeFmt = "abbrev"; ArrayList dataStack = new ArrayList(); [sysimport(dll="msvcrt.dll")] private static extern int wcsftime([nativetype(NativeType.NativeTypeLpwstr)] StringBuilder sb, int size, [nativetype(NativeType.NativeTypeLpwstr)] String format, tm timeptr); public void ProcessRequest(HttpContext context) { HttpRequest request = context.Request; HttpResponse response = context.Response; Stream outputStream = response.OutputStream; String FileName = request.PhysicalPath; FileStream ssiFileStream; try { // BUGBUG: impersonate? ssiFileStream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read); } catch (FileNotFoundException e) { throw new HttpException(HttpStatus.NotFound, "File not found"); } catch (IOException e) { throw new HttpException(HttpStatus.Forbidden, "Cannot open SSI file"); } catch (SecurityException e) { throw new HttpException(HttpStatus.Unauthorized, "Cannot open SSI file"); } byte[] data = ssiFileStream.ReadToEnd(); ssiFileStream.Close(); int position = 0, lastposition = 0; while (true) { position = findStartOfSsiCommand(data, lastposition); if (position == -1) { outputStream.WriteBytes(data, lastposition, data.Length - lastposition); if (dataStack.Count > 0) { SSIBuffer buffer = (SSIBuffer)dataStack[dataStack.Count - 1]; dataStack.Remove(dataStack.Count - 1); data = buffer.data; lastposition = buffer.position; continue; } break; } outputStream.WriteBytes(data, lastposition, position - lastposition); lastposition = ProcessSsiCommand(context, ref data, position); } } private int findStartOfSsiCommand(byte[] data, int lastposition) { int position = lastposition; while (position < data.Length - 3) { if (StartsWith(data, position, "")) return position + 3; } else if (StartsWith(data, position, "%>")) return position + 2; throw new HttpException(HttpStatus.InternalServerError, _errMsg); } private void DoConfig(byte[] data, ref int position) { // skip over any white space while ((position < data.Length) && Char.IsWhiteSpace((char)data[position])) position++; if (StartsWithI(data, position, "errmsg=")) { position += 7; _errMsg = GetString(data, ref position); } else if (StartsWithI(data, position, "timefmt=")) { position += 8; _timeFmt = GetString(data, ref position); } else if (StartsWithI(data, position, "sizefmt=")) { position += 8; _sizeFmt = GetString(data, ref position); } else throw new HttpException(HttpStatus.InternalServerError, _errMsg); } private void DoEcho(HttpContext context, byte[] data, ref int position) { // skip over any white space while ((position < data.Length) && Char.IsWhiteSpace((char)data[position])) position++; if (StartsWithI(data, position, "var=")) { position += 4; String varName = GetString(data, ref position); if (varName.Equals("DOCUMENT_NAME")) context.Response.Write(context.Request.PhysicalPath); else if (varName.Equals("DOCUMENT_URI")) context.Response.Write(context.Request.Path); else if (varName.Equals("QUERY_STRING_UNESCAPED")) context.Response.Write(HttpUtility.UrlDecode(context.Request.ServerVariables["QUERY_STRING"])); else if (varName.Equals("DATE_LOCAL")) printDate(context, DateTime.Now); else if (varName.Equals("DATE_GMT")) printDate(context, DateTime.Now.ToUniversalTime()); else if (varName.Equals("LAST_MODIFIED")) printModificationDate(context, context.Request.PhysicalPath); else context.Response.Write(context.Request.ServerVariables[varName]); } else throw new HttpException(HttpStatus.InternalServerError, _errMsg); } private void DoExec(HttpContext context, byte[] data, ref int position) { // skip over any white space while ((position < data.Length) && Char.IsWhiteSpace((char)data[position])) position++; if (StartsWithI(data, position, "cgi=")) { position += 4; String newUrl = GetString(data, ref position); if (newUrl[0] != '/') throw new HttpException(HttpStatus.InternalServerError, _errMsg); // save the SSI document's url String oldUrl = context.Request.RawUrl; // perform the new url request OutputHelpers.CgiRedirect(context, newUrl); // restore the SSI document's url context.RewritePath(oldUrl); } else if (StartsWithI(data, position, "cmd=")) { position += 4; String filename = GetString(data, ref position); NativeHandles natHandles = new NativeHandles(); // Use the CgiHandler functions to execute this file // without setting up all the cgi environment etc if (!CgiHandler.SetupIOAndStartProc(filename, null, CgiHandler._parentEnv, natHandles, 0)) throw new HttpException(HttpStatus.InternalServerError, _errMsg); NativeResponse respData = new NativeResponse(); CgiHandler.ReadResponse(natHandles.parentstdout, natHandles.childstdout, natHandles.procHandle, respData); byte[] responseData = new byte[respData.buflen]; PInvoke.Copy(respData.bufptr, responseData, 0, respData.buflen); context.Response.OutputStream.WriteBytes(responseData); CgiHandler.cgiCompletionCallback(respData.bufptr, natHandles.parentstdin, natHandles.childstdin); } else throw new HttpException(HttpStatus.InternalServerError, _errMsg); } private void DoLastMod(HttpContext context, byte[] data, ref int position) { // skip over any white space while ((position < data.Length) && Char.IsWhiteSpace((char)data[position])) position++; String fileName = GetFileName(context, data, ref position); printModificationDate(context, fileName); } private String GetFileName(HttpContext context, byte[] data, ref int position) { String fileName; if (StartsWithI(data, position, "file=")) { position += 5; fileName = GetString(data, ref position); // Only relative paths allowed // Should not be an absolute path or a path starting with .. if (fileName.StartsWith("..") || fileName[0] == '\\' || fileName[1] == ':') throw new HttpException(HttpStatus.InternalServerError, _errMsg); fileName = Path.Combine( Path.GetDirectory(context.Request.PhysicalPath), fileName); } else if (StartsWithI(data, position, "virtual=")) { position += 8; String Url = GetString(data, ref position); if (Url[0] != '/') throw new HttpException(HttpStatus.InternalServerError, _errMsg); fileName = context.Request.MapPath(Url); } else throw new HttpException(HttpStatus.InternalServerError, _errMsg); return fileName; } private void printModificationDate(HttpContext context, String fileName) { FileStream fs; try { // BUGBUG: impersonate? fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); } catch (Exception e) { throw new HttpException(HttpStatus.InternalServerError, _errMsg, e); } printDate(context, fs.LastWriteTime); fs.Close(); } private void printDate(HttpContext context, DateTime date) { tm timeStruct = new tm(date.Second, date.Minute, date.Hour, date.Day, date.Month - 1, date.Year - 1900); // BUGBUG: look at return value and check if we need bigger // StringBuilder StringBuilder formattedDate = new StringBuilder(256); wcsftime(formattedDate, 256, _timeFmt, timeStruct); context.Response.Write(formattedDate.ToString()); } private void DoSize(HttpContext context, byte[] data, ref int position) { // skip over any white space while ((position < data.Length) && Char.IsWhiteSpace((char)data[position])) position++; String fileName = GetFileName(context, data, ref position); printSize(context, fileName); } private void printSize(HttpContext context, String fileName) { FileStream fs; try { // BUGBUG: impersonate? fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); } catch (Exception e) { throw new HttpException(HttpStatus.InternalServerError, _errMsg, e); } int fileSize = (int)fs.GetLength(); fs.Close(); if (_sizeFmt.ToLower().Equals("bytes")) context.Response.Write(Int32.Format(fileSize, "n0")); else if (_sizeFmt.ToLower().Equals("abbrev")) context.Response.Write(Int32.Format(fileSize/1024, "n0")); else throw new HttpException(HttpStatus.InternalServerError, _errMsg); } // BUGBUG: no checking for infinite loop private void DoInclude(HttpContext context, ref byte[] data, ref int position, byte delimiter) { // skip over any white space while ((position < data.Length) && Char.IsWhiteSpace((char)data[position])) position++; String fileName = GetFileName(context, data, ref position); // eat up the trailing delimiter if (delimiter == '-') { if (StartsWith(data, position, "-->")) position += 3; } else // if (StartsWith(data, position, "%>")) position += 2; SSIBuffer buf = new SSIBuffer(data, position); dataStack.Add(buf); FileStream fs; try { // BUGBUG: impersonate? fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); } catch (Exception e) { throw new HttpException(HttpStatus.InternalServerError, _errMsg, e); } data = fs.ReadToEnd(); fs.Close(); position = 0; } private bool StartsWith(byte[] buf, int position, String prefix) { for (int i=0; i= buf.Length) || (buf[i + position] != prefix[i])) return false; } return true; } private bool StartsWithI(byte[] buf, int position, String prefix) { for (int i=0; i= buf.Length) || (Char.ToLower((char)buf[i + position]) != Char.ToLower(prefix[i]))) return false; } return true; } private String GetString(byte[] buf, ref int position) { StringBuilder sb = new StringBuilder(); if ((position >= buf.Length) || (buf[position] != '"')) throw new HttpException(HttpStatus.InternalServerError, _errMsg); position++; while ((position < buf.Length) && (buf[position] != '"')) { sb.Append((Char)buf[position]); position++; } if (position >= buf.Length) throw new HttpException(HttpStatus.InternalServerError, _errMsg); position++; return sb.ToString(); } public bool IsReusable() { _errMsg = "Error occured while parsing SSI document"; _timeFmt = "%A %B %#d %Y"; _sizeFmt = "abbrev"; dataStack = new ArrayList(); return true; } } }