Tuesday, February 22, 2011

Using Cookies with ASP


The ASP engine maintains Session state with cookies; if the user's browser won't accept cookies, the ASP engine cannot maintain Session state for that user. That's because there has to be some sure method of identifying a particular browser. Given the uniqueness of IP addresses, you'd think that they would be perfect for identifying a particular browser, but no such luck. Multiple users often share an IP address or block of addresses. That's what a proxy server does—it maps the user's "real" IP address to one or more public IP addresses. The proxy then makes the request via the shared address. Therefore, it's perfectly possible for two or more browsers to access your application from the same IP address. Enter the cookie.


Cookies are text strings that a browser sends to a Web site each time it requests a page from that site. You can use HTTP headers to manipulate the contents of cookies directly, although it's much easier to use the Response.Cookies collection methods. Of course, that's all that the Response.Cookies collection does—send HTTP headers to add, alter, or delete cookies.

Basically, there are two kinds of cookies:


  • In-memory cookies These are held in memory, which means they disappear when you close the browser.
  • Persistent cookies These are persisted to disk, which means they expire when you delete the file or when their expiration date arrives, whichever comes first.
The ASP SessionID cookie is an in-memory cookie.


Because some people don't want to accept any information that might identify them to a Web site, browsers generally have a setting that lets you refuse cookies. ASP sites typically don't work well if the browser doesn't accept cookies, because most ASP sites rely on the cookie so they can maintain session information for you.


The ASP engine writes a SessionID cookie as soon as the user requests the first file from an ASP application. The ASP SessionID cookie is in every way a normal cookie, except that it is usually invisible to your application. If you query the Request object cookie collection, you won't see the ASP SessionID cookie. You can prove it by creating the following ASP page script:




<%@Language=VBScript%>
<html>
<head>
<title>List Cookies</title>
</head>
<body>
<%
Dim V If Request.Cookies.Count > 0 then
For each V in Request.Cookies
Response.Write V & "=" & Request.Cookies(V) & "<BR>"
Next Else Response.Write "No Cookies"
End If
%>
</body>
</html>


Name the file and place it in a Web site. If you're using Visual InterDev, create a new Web project and call it ASPProject1, add a new ASP file, and place the code listing in it. Now use your browser to navigate to the page. You will see the text No Cookies. What happened?


Now, add one more line of code to the file, just before the end-of-script marker—the percent sign and right bracket (%>):


Response.Write "HTTP_COOKIE=" &
Request.ServerVariables("HTTP_COOKIE") & "<BR>"


This line of code displays the "raw" contents of the HTTP_COOKIE variable, without the filtering and organization applied by the ASP engine for the Request.Cookies collection. Run the file again (by refreshing the page). This time, you'll see No Cookies, and another line as well, that probably looks something like this:


HTTP_COOKIE=ASPSESSIONIDGGGQGQYE=DILDAIIBIFLCKEJCBLPCFCNI


That's the ASPSESSIONID cookie. It's a meaningless, semi-random ID that is highly likely to be unique. The ASP engine doesn't show it to you because you didn't generate it—the ASP engine did. I guess Microsoft thought it would be confusing to request the cookie count and get a 1 in response before ever setting a cookie yourself.


Consider one more interesting fact before we move on to a larger ASP project. Add this line to the end of the code listing:


Response.Write Session.SessionID


Refresh the page again. This time, you should see one additional line containing a long integer number like 411057027. The SessionID property doesn't return the SessionID cookie value at all! So will the "real" Session identifier please stand up? It turns out that the real SessionID is the long integer. The ASP engine generates the cookie as a long string of text for security reasons. It's much harder for a hacker to guess the long cookie string value than to guess the long integer value with which the cookie string is associated on the server.

Monday, February 21, 2011

Data, Data, Everywhere…


We have probably all come across John Naisbett's famous phrase "We are drowning in information, but starving for knowledge." This phrase takes on more truth with each passing day. Most corporate legacy systems have years of data on purchases, clients, products, etc. But it is just that - so much data. On its own it is meaningless; it is we and our users who have to make sense of it, and turn it into meaningful and useful information. So ironically, as more and more data piles up around us, we are confronted with the paradox of "the more data there is, the less information we have."

So the real question for us in this chapter is how can we distill that mountain of data into some useable knowledge? For example, how can a product manager at a company take all of the information that is in the various databases and use it to market products smarter? Or how can they see subtle trends that will permit a better use of limited advertising funds?

To take this to an extreme, consider the American Stock Exchange. With all of the data and all of the investment companies employing sophisticated algorithms to gain an edge, to a large extent securities trading has become a game of computers against computers. Humans are there to advise only on a meta level.

Understanding ASP Script


To start out with, each page must start with the tag <HTML> and end with </HTML>. Our page is sandwiched between these tags. These tell the browser that this is a web page and, armed with this knowledge, it knows what to do and how to show it:

<HTML>
<HEAD>
<TITLE>Our First ASP Script</TITLE>
</HEAD>
<H1><B>Our First ASP Script</B></H1>
<BODY> Let's count up to 5.
<BR>
<HR>
<% For iCounter = 1 to 5
Response.Write(iCounter) %>
<BR>
<% Next %>
<HR>
</BODY>
</HTML>


Everything between the <HEAD> and </HEAD> tags contain general information about the document, such as the title:

<HEAD>
<TITLE>Our First ASP Script</TITLE>
</HEAD>

Within these tags we place the <TITLE> and set it to Our First ASP Script. This is what shows up in the title of the browser. Titles should be descriptive because this is often used as a reference to visited sites. As a rule of thumb, you should be able to guess the contents of the page from the title alone.
Next comes the <BODY> tag:

<BODY>

Everything between this and the </BODY> tag encloses the HTML that is to be displayed in the browser, as well as our ASP code in our case here. Our ASP script is not dependent on the HTML tags. We are just adding them here for correctness. But we will see later on that some ASP specific commands come at the very top of the page.

Next we are using the <H1> tag:

<H1><B>Our First ASP Script</B></H1>

This stands for 'level-1 heading'. Headings come in six levels, H1 through H6, decreasing in importance. Next we sandwich our heading text Our First ASP Script between the <B> and </B> tags. This tells the browser to render any text between these two tags in bold. So, this HTML tells the recipient browser that we want to render a first level heading in bold text on the page.
Then we simply instruct the browser to display Let's count up to 5.We just type it in. Finally, we have a tag and a tag.

Let's count up to 5. <BR> <HR>

This is known as a Line Break and acts just like a hard carriage return. So the next thing rendered will be on the next line directly under the line with the <BR> tag. Lastly, for some gratuitous effects, we add the HTML tag <HR> for Horizontal Rule. This will draw a nice recessed line across the screen for us

Now we get into the ASP part of our script. The key character is the %. Everything between the % tags is VBScript:

<% For iCounter = 1 to 5 
Response.Write(iCounter) %>
<BR> <% Next %> <HR>
</BODY>
</HTML>

All of the scripts we will be writing are written in VBScript. Everything enclosed between the <%  %> tags is Active Server Page code. The VBScript variety of server code, which we are now writing, can have the % tags sprinkled throughout a standard HTML page. And just like HTML tags, every script opening tag <% must have a corresponding script closing tag %>. VBScript is the default scripting language of Active Server Pages. Active Server Pages will process any legitimate commands that are between the <% and %> tags.
Here's the first chunk of script again:

<% For iCounter = 1 to 5
Response.Write(iCounter) %>

So we are writing VBScript code here between the % tags. Using VB syntax, we are just looping 5 times. For each iteration through the loop, we are writing the result of iCounter to the browser. We use the .Write method of the ASP Response object to send information to the browser. Whatever we write is treated as a variant. Notice that we did not dimension iCounter. Any variable defaults to a variant, just like in Visual Basic 6. So if you wrote a script that had a line such as

<% Response.Write now %>

you would get the following result rendered on the browser:

After we output the value of iCounter to the browser, we send the HTML tag <BR>, for a hard break. So we must delimit our VBScript code with the %> right after we send the response - Write now %>.
Here's the next part of this page's script component:

<BR> <% Next %> <HR>

Here we place the HTML <BR> tag which will place the next number produced by our loop right underneath. And then we need the ASP code Next to complement our For loop. So the Next keyword must be enclosed in <% %> because it is VBScript, not HTML. You get the idea. We bracket the ASP specific commands within the % tags and leave the standard HTML code out in the open. When each of the five numbers are printed, we place another <HR> tag to give a nice effect. And to wrap things up, we end our page - as all HTML pages are ended with this:

</HTML>

There, you have written your first ASP page. And that's all there is to it. Of course, we will get a bit fancier with the output in the next example, but you now have the essence of an ASP page
You have probably noticed that HTML commands are really designed to specify the logical structure of the document, far more than the physical layout. For example, our HTML tags tell the browser what the heading should be by enclosing it within the <H1></H1> tags, but the browser can find its best way of displaying the heading. If we shrink or stretch the browser window, it is responsible for reformatting as best it can to adapt to the new size.

Background to ADO


In the early days of computing, dumb terminals were wired to powerful mainframe computers. The centralized Information Services (IS) department of a company ran the computing show. The mainframe gurus told us what we could and could not do. Then in August of 1981, the first IBM personal computer was released and the world changed. Control was eventually wrested from the centralized IS department at companies and flowed to every individual with a personal computer.

Each personal computer had its own CPU and hard drive to run programs and store data. Centralized computing for many day to day activities disintegrated and each person with a personal computer took individual control if their data destiny. There was talk of big iron mainframes going the way of the dinosaur - who needed them? We had just as much processing power on our desktops. Life was good.

But there were problems too. For example, lots of individual computers sitting on people's desks, and all wanting to share information and common data. Out of the many desktop solutions to this need to access data by distributed computing stations was Data Access Objects (DAO). We've already learned about (DAO) and how easy it is to create desktop and file server database programs.

Now, with VB6.0, a brand new Jet database engine 3.51 just made this solution stronger. In fact, Jet 3.51 is faster and more robust than Jet 3.5 that shipped with VB 5.0. Microsoft wanted to enhance a proven, strong database access solution and, when developing desktop database solutions using MDB or ISAM files, Microsoft says the combination of Jet and DAO is definitely the way to go. Microsoft has upgraded DAO and will continue to support this approach for the foreseeable future. And don't forget, DAO is the most popular desktop database access method around! So the installed base of solid, robust applications using DAO is tremendous.

But DAO's days are numbered. It is a technology that will not be enhanced any further. So we programmers must learn ADO because that is the future for us.

Validating User Input to Avoid Attacks



To protect against vulnerabilities such as script injection and cross-site scripting, user input can be verified and rejected, or an application can remove harmful characters and continue processing. This topic provides example code that uses regular expressions to verify user input.

The regular expression, ^[\w\.:\?&=/]*$, searches for a complete string (from beginning to end) that contains only the following characters:

  • alphanumeric or underscore (_)

  • periods (.)

  • colons (:)

  • question marks (?)

  • ampersands (&)

  • equal signs (=)

  • forward slashes (/)






<%@ LANGUAGE="VBScript" %>
<% Response.CodePage = 1252
If ValidateInput(MyUrl) Then
Response.Redirect (myURL)
Else
Response.Write("URL was invalid.")
End If
Function ValidateInput(sInput)
Dim reValid Set reValid = New RegExp
reValid.Pattern = "^[\w\.:\?&=/]*$"
reValid.MultiLine = False
reValid.Global = True
ValidateInput = reValid.
Test(sInput)
End Function %>

ASP Security Issues

The following is a list of issues to keep in mind when you are developing Web pages using ASP.

  • Never trust user input to be of an appropriate size or contain appropriate characters. Always verify user input before using it to make decisions. The best option is to create a COM+ component which you can call from an ASP page to verify user input. You can also use the Server.HTMLEncode method, the Server.URLEncode method, or one of the code examples at the bottom of this page.
  • Do not create database connection strings in an ASP page by concatenating strings of user input together. A malicious attacker can inject code in their input to gain access to your database. If you are using a SQL database, use stored procedures for creating database connection strings.
  • Do not use the default SQL administrator account name, sa. Everyone who uses SQL knows that the sa account exists. Create a different SQL administrative account with a strong password and delete the sa account.
  • Before you store client user passwords, hash them, base64 encode them, or use Server.HTMLEncode or Server.URLEncode to encode them. You can also use one of the code examples at the bottom of this page to verify the characters in the client's password.
  • Do not put administrative account names or passwords in administration scripts or ASP pages.
  • Do not make decisions in your code based on request headers because header data can be fabricated by a malicious user. Before using request data, always encode it or use one of the code examples below to verify the characters it contains.
  • Do not store secure data in cookies or hidden input fields in your Web pages.
  • Always use Secure Sockets Layer (SSL) for session-based applications so that you never risk sending session cookies without encrypting them. If session cookies are not encrypted, a malicious user can use a session cookie from one application to gain entry to another application that resides in the same process as the first application.
  • When writing ISAPI applications, filters, or COM+ objects, watch out for buffer over-runs caused by assuming sizes of variables and data. Also watch out for canonicalization issues that can be caused by interpreting things like absolute path names or URLs as relative path names or URLs. 
  • When you switch an ASP application from running in Single Threaded Apartment (STA) to Multi-Threaded Apartment (MTA) (or from MTA to STA), the impersonation token becomes obsolete. This can cause the application to run with no impersonation, effectively letting it run with the identity of the process which might allow access to other resources. If you must switch threading models, disable the application and unload it before you make the change.

Count Page Hit in Classic ASP

It is important to know how many hits your Web pages get. This data helps determine how changes to your Web site may affect your customers' viewing habits. More importantly, it provides useful insight as to how customers are navigating through your site and where ads should be placed. Sites with high trafficking have higher advertisement prices associated with them. The data gathered from a page hit counter provides you with trafficking information to begin negotiating advertisement prices for your Web pages.

The Page Counter component uses an internal object to record page hit-count totals for all pages on the server. At regular intervals, this object saves all information to a text file so that no counts are lost due to power loss or system failure. The Page Counter component uses the following three methods:

  • Hits(). This displays the number of hits for a Web page. The default is the current page.
  • PageHit(). This increments the hit count for the current page.
  • Reset(). This resets the hit count for a page to zero. The default is the current page.
 An in-use sample of this script can be seen at the bottom of the Displayad.asp script. To place a page hit counter on a Web page, place the following script where you want the counter displayed on the page:


<% Set pageCount = Server.CreateObject("MSWC.PageCounter") %>
<% pageCount.PageHit %>
You are visitor number <% =pageCount.Hits %> to this Web site.

Creating an ad images data file


An ad images data file is created to provide information about the ads to be displayed. By placing the data in one text file, when changes need to be made, you only need to change the data in one location. The ASP page (with the logic in the Include file) sends data in the ad images data file to the Ad Rotator component. The component then selects an ad for display.


The data file is divided into two sections that are separated by an asterisk (*). The first section provides information common to all the ads to be displayed. The second section lists data relevant to each ad.

The following outlines the structure of an ad images data file:
REDIRECTION. URL, the path and name of the ASP file that redirects browsers that select ad images.
WIDTH. The width of ad images in pixels. Default is 440.
HEIGHT. The height of ad images in pixels. Default is 60.
BORDER. The border thickness around ad images. Default is 1.
*. Separates the first section from the second section.
AdURL. Virtual path and filename of the image file containing the advertisement.
AdHomeURL. URL to jump to when this link is selected. To indicate there is no link, use a hyphen.
Text. Text to display if browser does not support graphics.
Impressions. An integer indicating the relative weight, or probability, that this ad will be selected for display. For example, if two ads were displayed, one with an impression of 3 and the other with 7, then the one with 3 would have a 30 percent probability of being selected, while the other would have a 70 percent probability.

Open a new file in your text editor, paste in the following script, and save the file as Adimagedata.txt:




REDIRECT adrotatorredirect.asp
WIDTH 250
HEIGHT 60
BORDER 0
*
' separates the general data from the image information
images/windows_logo.gif
http://www.microsoft.com/windows Microsoft Windows
2
images/office_logo.gif
http://www.microsoft.com/office
Office 2000
3

Ad Rotator Include file

Include files are used to store information that will be used by more than one ASP or HTML file. By creating an Ad Rotator Include file, when changes need to be made to specific information, you only need to change the information in one location. This lesson will guide you in creating an Ad Rotator Include file containing a function named getAd(). This function randomly selects ads to display on your ASP pages. You can call this file from within any ASP page intended to display the rotated ads. When you test calling the Include file from an ASP page


Open a new file in your text editor, paste in the following script, and save the file as Adrotatorlogic.inc

<% Function getAd()
Dim load
'Create an instance of the AdRotator component
Set load=Server.CreateObject("MSWC.AdRotator")
'Set the target frame, if any. This is the frame
'where the URL will open up. If the HTML page does
'not find the target name, the URL will be opened
'in a new window. load.
TargetFrame = "Target = new"
'Get a random advertisement from the text file.
getAd = load.GetAdvertisement("adimagedata.txt")
End Function %>

Friday, February 18, 2011

Dynamically Include Files in Active Server Pages

Since Active Server Pages (ASP) pages are compiled and executed before being sent to the client (as static HTML), trying to use a variable in place of a file name in a Server Side Include () fails. To include a file in your ASP page, you could include code similar to the following that reads in a file and displays it.


<% ' The name of the file to display was passed by a form using GET method
infil = Request.QueryString("file")
If infil <&>"" then
set fso = Server.CreateObject("Scripting.FileSystemObject")
set fil = fso.OpenTextFile(infil)
outstring = fil.ReadAll
' PRE tags preserve the format of the file
Response.write "<PRE>" & outstring & "</PRE><BR>"
End If
%>

Response.Flush

One common complaint about response buffering is that users perceive ASP pages as being less responsive (even though the overall response time is improved) because they have to wait for the entire page to be generated before they start to see anything. For long-running pages, you can turn response buffering off by setting Response.Buffer = False. However, a better strategy is to utilize the Response.Flush method. This method flushes all HTML that has been painted by ASP to the browser. For example, after painting 100 rows of a 1,000-row table, ASP can call Response.Flush to force the painted results to the browser; this allows the user to see the first 100 rows before the remaining rows are ready. This technique can give you the best of both worlds-response buffering combined with the gradual presentation of data to the browser.

The other common complaint about response buffering is that it can use a lot of server memory when generating very large pages. Leaving aside the wisdom of generating large pages, this problem can also be addressed with judicious use of Response.Flush.

Response Buffering

You can buffer a whole page worth of output by turning on "response buffering." This minimizes the amount of writes to the browser and thus improves overall performance. Each write has a lot of overhead (both in IIS and in the amount of data sent down the wire), so the fewer the writes there are, the better. TCP/IP works much more efficiently when it can send a few large blocks of data than when it has to send many small blocks because of its slow start and Nagling algorithms (used to minimize network congestion).

There are two ways of turning response buffering on. First, you can turn on response buffering for an entire application, using the Internet Services Manager. This is the recommended approach and response buffering is turned on by default for new ASP applications in IIS 4.0 and IIS 5.0. Second, on a page-by-page basis, you can enable response buffering by placing the following line of code near the top of the ASP page:

<% Response.Buffer = True %>




This line of code must be executed before any response data has been written to the browser (that is, before any HTML appears in the ASP script and before any Cookies have been set using the Response.Cookies collection). In general, it is best to turn response buffering on for an entire Application. This allows you to avoid the above line of code on every page.

Cache Frequently-Used Data in the Application or Session Objects

The ASP Application and Session objects provide convenient containers for caching data in memory. You can assign data to both Application and Session objects, and this data will remain in memory between HTTP calls. Session data is stored per user, while Application data is shared between all users.

At what point do you load data into the Application or Session? Often, the data is loaded when an Application or Session starts. To load data during Application or Session startup, add appropriate code to Application_OnStart() or Session_OnStart(), respectively. These functions should be located in Global.asa; if they are not, you can add these functions. You

can also load the data when it's first needed. To do this, add some code (or write a reusable script function) to your ASP page that checks for the existence of the data and loads the data if it's not there. This is an example of the classic performance technique known as lazy evaluation-don't calculate something until you know you need it. An example:



<%
Function GetEmploymentStatusList
Dim d
d = Application("EmploymentStatusList")
If d = "" Then
' FetchEmploymentStatusList function (not shown)
' fetches data from DB, returns an Array
d = FetchEmploymentStatusList()
Application("EmploymentStatusList") = d
End If
GetEmploymentStatusList = d
End Function
%>


Similar functions could be written for each chunk of data needed.


In what format should the data be stored? Any variant type can be stored, since all script variables are variants. For instance, you can store strings, integers, or arrays. Often,you'll be storing the contents of an ADO recordset in one of these variable types. To get data out of an ADO recordset, you can manually copy the data into VBScript variables, one field at a time. It's faster and easier to use one of the ADO recordset persistence functions GetRows(), GetString() or Save() (ADO 2.5). here's a function that demonstrates using GetRows() to return an array of recordset data:




' Get Recordset, return as an Array
Function FetchEmploymentStatusList
Dim rs
Set rs = CreateObject("ADODB.Recordset")
rs.Open "select StatusName, StatusID from EmployeeStatus", _
"dsn=employees;uid=sa;pwd=;"
FetchEmploymentStatusList = rs.GetRows() " Return data as an Array
rs.Close
Set rs = Nothing
End Function




When you store data in Application or Session scope, the data will remain there until you programmatically change it, the Session expires, or the Web application is restarted. What if the data needs to be updated? To manually force an update of Application data, you can call into an administrator-access-only ASP page that updates the data. Alternatively, you can automatically refresh your data periodically through a function.



Be aware that caching large arrays in Session or Application objects is not a good idea.Before you can access any element of the array, the semantics of the scripting languages require that a temporary copy of the entire array be made. For example, if you cache a 100,000-element array of strings that maps U.S. zip codes to local weather stations in the

Application object, ASP must first copy all 100,000 weather stations into a temporary array before it can extract just one string. In this case, it would be much better to build a custom component with a custom method to store the weather stations—or to use one of the dictionary components.




Cache Frequently-Used Data on the Web Server

A typical ASP page retrieves data from a back-end data store, then paints the results into Hypertext Markup Language (HTML). Regardless of the speed of our database, retrieving data from memory is a lot faster than retrieving data from a back-end data store. Reading data from a local hard disk is also usually faster than retrieving data from a database. Therefore, we can usually increase performance by caching data on the Web server, either in memory or on disk.

If you cache the right stuff, you can see impressive boosts in performance. For a cache to be effective, it must hold data that is reused frequently, and that data must be (moderately) expensive to recompute. A cache full of stale data is a waste of memory.

Data that does not change frequently makes good candidates for caching, because you don't have to worry about synchronizing the data with the database over time. Combo-box lists, reference tables, DHTML scraps, Extensible Markup Language (XML) strings, menu items, and site configuration variables (including data source names (DSNs), Internet Protocol (IP) addresses, and Web paths) are good candidates for caching. Note that We can cache the presentation of data rather than the data itself. If an ASP page changes infrequently and is expensive to cache (for example, your entire product catalog), consider pregenerating the HTML, rather than repainting it for every request.

Where should data be cached, and what are some caching strategies? Data is often cached either in the Web server's memory or on the Web server's disks. The next article will cover these Issues