The last few weeks I have been battling with a Vendor application that uses a mix of Classic ASP and ASP.Net. Not to keep the beating to myself, I will like to share some of my bruised ego.
We will focus on the Classic ASP code. There are many reasons for this singular focus; and inclusive:
- Classic ASP was introduced in 1996
- It was Microsoft’s first server side scripting engine for generating web pages
- It is based on VBScript and JScript
- ASP 2.0 provided six built-in objects: Application, ASPError, Request, Response, Server, and Session
- It has being in support for accessing COM and Dlls
As a tool developed in 1996, almost 20 years ago, it is increasingly difficult to elegantly support it.
Let us create a sample app
<html> <head> <title> Err Suppressed </title> </head <@% Page Language=VBScript Explicit=True Debug=True %> <% Response.Buffer = True %> <!--#include file="errorHandler/errorHandler.inc"--> <% Const ERR_HANDLER_CUSTOMIZED = "/errorHandler/errorHandler500Customized.asp" Const ERR_TYPE_DIVIDEBYZERO = 1 Const ERR_TYPE_OBJECT_INVALIDMETHOD = 2 Const ON_ERROR_RESUME_NEXT = false Const ON_ERROR_PROCEED_ERRORHANDLERPAGE = false dim strDate dim iNumberofEntries dim objDictModule dim allKeys dim allItems dim dictKey dim dictItem Dim i Dim iMax dim objNullObject dim objErr dim numerator dim divisor dim result dim strItem dim iErrType dim strPagename strPagename = Request.ServerVariables("SCRIPT_NAME") if (ON_ERROR_RESUME_NEXT) then On error resume next end if iErrType = ERR_TYPE_OBJECT_INVALIDMETHOD iErrType = ERR_TYPE_DIVIDEBYZERO if (iErrType = ERR_TYPE_DIVIDEBYZERO) then numerator = 1000 divisor = 0 result = a / b elseif (iErrType = ERR_TYPE_OBJECT_INVALIDMETHOD) then objNullObject.Sleep() end if set objErr = Err set objDictModule = getErrorObjectAsDictionary(objErr) set Session(ERR_OBJECT_CUSTOM) = objDictModule if ( (Err.Number <> 0) and (ON_ERROR_PROCEED_ERRORHANDLERPAGE = true) ) Then 'store calling page name Session(ERR_OBJECT_CUSTOM).add "Err.Referrer", strPagename Response.Redirect ERR_HANDLER_CUSTOMIZED end if %>
In the code above, we have intentionally added the usual suspects; that is things we know will cause trouble:
- Divide by 0
- Reference a null object ( objNullObject.Sleep )
IIS Configuration – ASP
Let us make sure that IIS is sufficiently for debugging ASP.
- Via “Control Panel” \ “Administrative Tools” \ Internet Information Services (IIS) Manager
- In the left panel, Under Sites, Select the Web Site
- In the right panel, make sure that the Features View is active
- In the Features view, within the IIS panel, select ASP
The important configurable items are:
- Debugging Properties – Set to True
- Calculate Line Numbers
- Catch COM Component Exceptions
- Enable Client-side debugging
- Enable Log Error Requests
- Enable Server-side Debugging
- Log Errors to NT Log
- Send Errors to Browser
- Script Language
Upon launching a Web Browser, IE in this case, we run into a ditch.
Here is what we get:
Not a very intuitive error message as we had “Show Friendly HTTP error messages“.
More Intuitive Message
Via “Internet Options”, we turned off “Show friendly HTTP error messages”.
And, we now have a good error number, error message, and source file name & offending line number.
IIS Logs also contain helpful data.
Our IIS Log contains definite and helpful error; here they are:
- We have .Net CLR2 and CLR3 installed
- Our error line is 57
- Our error code is 800a006
- Our error description is Overflow
Sample Code – “On Error Resume Next”
Let us change the code a bit by setting “On error resume next”
'Const ON_ERROR_RESUME_NEXT = false Const ON_ERROR_RESUME_NEXT = true
Once we set “On error resume next”, our error is no longer visible …
Browser – IE
It is obvious that silencing our errors is a bit problematic; as we still have them, but we are not aware of them. Furthermore, the work we greatly want to do, is yet undone. Let us handle our error, and send the user to an informative error page.
'Const ON_ERROR_PROCEED_ERRORHANDLERPAGE = false Const ON_ERROR_PROCEED_ERRORHANDLERPAGE = true
As we have now indicated that we want to handle our error, though it is silent, we wrote additional modules:
Classic ASP supports a couple of ways to organize code, source code modules & Com components. In this post, we will take the quick and simple path of having the code in an included file (errorhandler/errorHandler.inc).
Though not required, we placed in its own folder, as well.
Here is our included file.
<% Const ERR_OBJECT_CUSTOM = "customErrorCollection" Function getErrorObjectAsDictionary (objErr) Dim objList Dim objASPError Set objList = CreateObject("Scripting.Dictionary") objList.add "Err.Number", objErr.Number objList.add "Err.Description", objErr.Description set objASPError = Server.GetLastError() objList.add "ASPError.ASPCode", objASPError.ASPCode objList.add "ASPError.ASPDescription", objASPError.ASPDescription objList.add "ASPError.Description", objASPError.Description objList.add "ASPError.Source", objASPError.Source objList.add "ASPError.Number", objASPError.Number objList.add "ASPError.File", objASPError.File objList.add "ASPError.Line", objASPError.Line rem we used set as objList is an object and not a simple datatype (int\string) Set getErrorObjectAsDictionary = objList End Function %>
The errorHandler.inc is an interesting code-line:
- We are a generic collection object (Scripting.Dictionary)
- We captured both the err.number and err.description properties into our collection
- We also performed a Server.GetLastError call and captured the resultant object’s property, as well
- Noticed that we used set at the end of the function to return a complex object
<html> <head> <title> Err Handler - 500 - Customized </title> </head> <@% Page Language=VBScript Explicit=True Debug=True EnableSessionState=True %> <!--#include file="errorHandler.inc"--> <body> <% Dim objError Dim objDictModule Dim errNumber Dim errDescription Dim objErr Dim objErrDict Dim strBuffer Response.Clear %> <table border="1"> <tbody> <tr> <td> <table border="0" width="653"> <tbody> <tr style="background-color: #ff9900;"> <td colspan=3 col align='center'><strong>Error Items</strong></td> </tr> <tr style="background-color: Gainsboro;"> <td> </td> <td><strong>Item</strong></td> <td><strong>Value</strong></td> </tr> <!-- Access Err Object --> <tr style="background-color: beige;"> <td> </td> <td><strong>Err.Number</strong></td> <td><strong><%= CSTR(Err.Number) %></strong></td> </tr> <tr style="background-color: beige;"> <td> </td> <td><strong>Err.Description</strong></td> <td><strong><%= Err.Description %></strong></td> </tr> <% strBuffer = "" CONST COLOR_ROW = "beige" CONST COLOR_ROW_ALTERNATE = "blanchedalmond" Dim strRowColor Dim strItem If isObject(Session(ERR_OBJECT_CUSTOM)) = false then else set objDictModule = Session(ERR_OBJECT_CUSTOM) if (objDictModule is Nothing) Then else 'retrieve all the keys and items from the Dictionary and print them out allKeys = objDictModule.Keys 'Get all the keys into an array allItems = objDictModule.Items 'Get all the items into an array For i = 0 To objDictModule.Count - 1 'Iterate through the array dictKey = allKeys(i) 'This is the key value dictItem = allItems(i) 'This is the item value if ( (i mod 2) = 0) then strRowColor = COLOR_ROW else strRowColor = COLOR_ROW_ALTERNATE end if strItem = " <tr style='background-color: " & strRowColor & "';>" strBuffer = strBuffer & strItem strItem = " <td>" & cstr(i+1) & "</td> " strBuffer = strBuffer & strItem strItem = " <td>" & dictKey & "</td> " strBuffer = strBuffer & strItem strItem = " <td>" & dictItem & "</td> " strBuffer = strBuffer & strItem strItem = "</tr> " strBuffer = strBuffer & strItem Next end if end if Response.Write strBuffer %></tbody> </table> </td> </tr> </tbody> </table> </body> </html> %>
Here is a quick explanation
- We checked Err.Number and Err.Description
- We checked the Session Object that was “set” in the previous page
- Notice the use of IsObject to ensure that our session variable is previously set
From the screen above, we will notice a few things:
- We lost our original error; that is why when the new page calls err.number and err.description we have 0 and empty
- Thankfully, our session data was preserved
It appears that when we use “on error resume next” and post to another page, our err object ( Err.Number and Err.Description ) is automatically reset.
From the example above, we can see that the “on error resume next” statement, prevents error trapping through code.
What about error trapping that is implicitly handled in web.config
Edit Custom Error Page:
Here is how to configure Error Page via “IIS Management Console”
In our case, we tweaked our website’s configuration quite a bit. We reviewed and played around with the Application Pool and the actual web site.
Error Message: This site is in an application pool that is running in Classic mode. When running in this mode, custom errors apply to all content except ASP.Net content.
The error message hints that we need to change our Application Pool’s managed pipeline from Classic to Integrated.
Error Pages – Feature Settings
It is also important that we set/review the default “Error Pages” settings.
We want to set “Error Page Settings” / “Custom Error Pages” to “Custom error pages”.
If set to :
- Detailed errors (everyone sees the detailed errors)
- Detailed errors for local requests and custom error pages for remote requests ( when viewed locally on the web server one sees the detailed error message; and when viewed away from the web server, one sees our custom error page)
Here is what our web.config looks like:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <defaultDocument> <files> <add value="ErrSupressed.asp" /> </files> </defaultDocument> <tracing> <traceFailedRequests> <remove path="*" /> <add path="*"> <traceAreas> <add provider="ASP" verbosity="Verbose" /> <add provider="ASPNET" areas="Infrastructure,Module,Page,AppServices" verbosity="Verbose" /> <add provider="ISAPI Extension" verbosity="Verbose" /> <add provider="WWW Server" areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module,FastCGI" verbosity="Verbose" /> </traceAreas> <failureDefinitions timeTaken="00:00:00" statusCodes="100-999" /> </add> </traceFailedRequests> </tracing> <httpErrors errorMode="Custom"> <remove statusCode="500" subStatusCode="-1" /> <error statusCode="500" prefixLanguageFilePath="" path="/errorHandler/errorHandler500Customized.asp" responseMode="ExecuteURL" /> </httpErrors> </system.webServer> </configuration>
There are a few things going on in the web.config file indicated above; and those are:
- We are using our great friend – traceFailedRequests; whenever an error occurs an XML file is generated
- We are using trapping on HTTPErrors
- We are using ErrorMode is Custom :- This is we have our our own custom code
- HTTP Error = 500
- We call /errorHandler/errorHandler500Customized.asp
- responseMode = ExecuteURL
Once we add a web.config page, we will have instituted an Application wide error handling pathway.
The Application wide pathway will work for all cases, except when “on error resume next” is in-effect.
How do we test?
How do we investigate whether we can handle errors implicitly?
We will remove “on error resume next”.
Const ON_ERROR_RESUME_NEXT = false
rem Const ON_ERROR_RESUME_NEXT = true
We will not trod down the Debugging trek; and so we will skip initiating the debugger by choosing “No, cancel debugging“.
Here is what we see when a code error and IIS itself triggers our error page
We can see that we have lost useful error data. Earlier we preserved them by saving them to session variables and then transferring processing to the Error Page.
Spent all weekend trying to get to them when IIS auto-invokes the error page, but not smart enough.
On error goto 0
“On error goto 0” return us back to sanity. I will suggest that for each “on error resume next”, a corresponding “on error goto 0” be added as a complement.
Modern languages idiom rely on try/catch/finally exception paradigm.
To make it easier to share and review our simple App, we have place it on Github.
Where did Classic ASP come from?
Accordingly to Wikipedia, Classic ASP was introduced as part of NT Option Pack in 1996. And, discontinued 4 years later in 2000.
NT Option Pack is one of the most important product release from Microsoft. Keeping in mind that it was a midterm release between Windows NT 4.0 and Windows 2000, and Microsoft was trying to stem the tide of competing products; and so everything along with the Kitchen Sink and the proverbial workman’s hamburger was included.
According to Windows NT Option Pack, here is what was bundled within it:
- Certificate Server – Microsoft Certificate Server provides customization services for issuing and managing certificates used in software security systems employing public-key cryptography.
- FrontPage Server Extensions
- Index Server – It allows you to easily perform full-text searches and retrieve all types of information from any Web browser.
- Internet Connection Services for RAS – Remote Access Service
- Internet Information Server (IIS) version 4.0
- Mail and News Services – Microsoft SMTP Service uses the standard Internet protocol Simple Mail Transfer Protocol (SMTP) to transport and deliver messages
- The Microsoft NNTP Service – Newsgroup
- The Microsoft Data Access Components (MDAC) – ActiveX Data Objects and the Microsoft Access driver
- Microsoft Management Console (MMC) – Uniform interface for managing server application
- Microsoft Message Queue Server (MSMQ) – communicate with other application programs quickly, reliably, and asynchronously by sending and receiving messages
- Microsoft Transaction Server (MTS) – component-based transaction processing system for developing, deploying, and managing high-performance, scalable, and robust applications
- Site Server – Comprehensive Web site environment for enhancing, deploying, and managing rich intranet and Internet Web sites
- Microsoft SNA Server – comprehensive gateway and application integration platform that enables communications with midrange ( AS/40)0 and mainframe systems
- Windows Script Host – a language-independent scripting host for ActiveX™ scripting engines
Once again, I will dedicate this post to a public commit-er, former Microsoft engineer, Eric Lippert. I referenced an important blog post by him in the Reference section below.
Eric blogs @ http://ericlippert.com/ and he has an interesting post @ “Eric Lippert Dissects CVE-2014-6332, a 19 year-old Microsoft bug” ( http://security.coverity.com/blog/2014/Nov/eric-lippert-dissects-cve-2014-6332-a-19-year-old-microsoft-bug.html )
For a product released in 1996 and deprecated in 2000; ASP has obvious staying power.
For the sake of the numerous companies and products that continue to rely on it, I wish keen attention, knowledge and courage.
For sustenance engineers, keep an eye out for “On error resume next“.
Reference – httpErrors and customErrors
- httpErrors Element [IIS Settings Schema]
- customErrors Element (ASP.NET Settings Schema)
Blog and Q/A
- Evagoras Charalambous – A custom ‘404 Page Not Found’ in ASP.NET
- Why setting customerros in web.config does not work
IIS – Custom Errors – Sample Code
- How To Use the ASPError Object to Build a Custom Error-Handling Page in Internet Information Server
- Displaying Safe Error Messages
- PageErrorPage Property
3rd Party Vendor
- How to create custom error pages in IIS 7.5 with ASP.Net
- Creating custom Error handling pages
- Turning off IIS8 Custom Errors for Classic ASP – Potential bug in IIS?
Storing & Transfering Data
- Transfering Data between ASP.Net web pages
- ASP.NET – 10 Tips for Writing High-Performance Web Applications
- Dictionary Object
- Collection of objects in Classic ASP Using VBScript
- Don’t redirect after setting a Session variable (or do it right)
- How to use Session and Application variables in an ASP program
- Asp.Net adding objects to a session variable ( C# )
- A matter of context
- State management with Context.Items in ASP.NET
- Passing values from a page to another by means of the Context Object
- ASP Best Practices
ASP Error Handling – Reference
- ASP ASPError Object
ASP Error Handling – Sample
- Error Handling in VBScript – Part 1
- Application Level Error Handling in ASP.NET
- How to Use the #include Directive
- Testing if Object is nothing results in Object Required error
- ASP.NET Error Handling By Erik Reitan|last updated September 8, 2014
Move to new page
“On Error Resume Next ” Stories
- On error resume next considered harmful (by palo mraz)
- Error Handling in VBScript / Part 1 ( Eric Lippert, a principal developer on the C# compiler team )