Tuesday, May 29, 2007

Flaw with buttons in the GridView pager

For unknown reason the GridView pager in Numeric and NumericFirstLast modes does not take into consideration values of the PreviousPageText and NextPageText properties from the pager settings. Instead an ellipsis is shown. Of course, you may say that the buttons with the ellipsis implement a bit different functionality. Yes, they do. But the problem leaves - text on these buttons is not customizable. Sometimes it becomes problematic especially if your customer is too pernickety. To solve this flaw you can use next approach:
GridView1.RowDataBound+=new GridViewRowEventHandler(GridView1_RowDataBound);
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.Pager)
{
Table pagerTable = (Table)e.Row.Cells[0].Controls[0];
TableRow pagerRow = pagerTable.Rows[0];
PagerSettings pagerSettings = ((GridView)sender).PagerSettings;
int cellsCount = pagerRow.Cells.Count;
if (pagerSettings.Mode == PagerButtons.Numeric
|| pagerSettings.Mode == PagerButtons.NumericFirstLast)
{
int prevButtonIndex = pagerSettings.Mode==PagerButtons.Numeric ? 0 : 1;
int nextButtonIndex = pagerSettings.Mode==PagerButtons.Numeric ? cellsCount-1 : cellsCount-2;
if (prevButtonIndex < cellsCount)
{
//check whether previous button exists
LinkButton btnPrev = pagerRow.Cells[prevButtonIndex].Controls[0] as LinkButton;
if (btnPrev != null && btnPrev.Text.IndexOf("...") != -1)
btnPrev.Text = pagerSettings.PreviousPageText;
}
if (nextButtonIndex > 0 && nextButtonIndex < cellsCount)
{
//check whether next button exists
LinkButton btnNext = pagerRow.Cells[nextButtonIndex].Controls[0] as LinkButton;
if (btnNext != null && btnNext.Text.IndexOf("...") != -1)
btnNext.Text = pagerSettings.NextPageText;
}
}
}
}

Update: It turns that it is rather simply to completely simulate the Previous and Next buttons. Add following snippets to the code above.
if (btnPrev != null && btnPrev.Text.IndexOf("...") != -1)
{
...
btnPrev.CommandName = "Page";
btnPrev.CommandArgument = "Prev";
}
...
if (btnNext != null && btnNext.Text.IndexOf("...") != -1)
{
...
btnNext.CommandName = "Page";
btnNext.CommandArgument = "Next";
}

Sunday, May 20, 2007

Image-based CAPTCHA. Demo Project

Demo project that illustrates the idea of the image-based CAPTCHA.

The code overview

The proposed image-based CAPTCHA control works in such a way: a visitor sees a picture with a distorted part and he has to click elsewhere in the anomalous region boundaries. The point he clicks on will be marked with a red spot.

The control fulfils a double functionality, it renders its HTML content and forms the picture itself. It has two child controls: an image, and a hidden field that serves to store coordinates of the visitor's chosen point. The image URL forms by adding the special parameter to the currently requested URL. When the request to this new URL comes, the control interrupts the usual process of page loading and writes the image as a byte array in the response.
protected override void OnInit(EventArgs e)
{
if (HttpContext.Current.Request[queryKey] != null)
DrawImage();
.....
}
private void DrawImage()
{
Bitmap bitmap;

//the image creation goes here
....

HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ContentType = "image/jpg";
bitmap.Save(HttpContext.Current.Response.OutputStream, ImageFormat.Jpeg);
HttpContext.Current.Response.End();
}

This approach allows to compound all the functionality in a single control. In a heavily loaded environment, it is better to take out the image creation elsewhere to another place - for example, to the HttpHandler - to avoid complicating the creation of the page where the control is placed on.

The coordinates of the point where the visitor clicks is calculated and visualized by means of JavaScript (tested on IE 6.0, Firefox 1.0+, Opera 9.1). They are stored in the hidden field in order to be accessible on the server side.
function captchaClicked(hidID,e)
{
var sender = e.target || e.srcElement;
var offsetX, offsetY;

//the calculation of the the clicked point's coordinates
if (e.offsetX) //IE
{
offsetX = e.offsetX;
offsetY = e.offsetY;
}
else if (e.pageX) //Firefox, Opera
{
var left = sender.offsetLeft;
var top = sender.offsetTop;
var parentNode = sender.offsetParent;
while(parentNode != null && parentNode.offsetLeft != null && parentNode.offsetTop != null){
left += parentNode.offsetLeft;
top += parentNode.offsetTop;
parentNode = parentNode.offsetParent;
}
offsetX = e.pageX - left;
offsetY = e.pageY - top;
}

//storing of the coordinates
document.getElementById(hidID).value = offsetX+","+offsetY;

//the creation of the little red spot to mark the clicked point
var spot = document.getElementById("spotOnCaptha");
if (!spot)
{
spot = document.createElement("span");
spot.id = "spotOnCaptha";
spot.style.height = "3px";
spot.style.width = "3px";
spot.style.fontSize="1px";
spot.style.backgroundColor="red";
spot.style.position="absolute";
spot.style.zIndex = 100;
sender.parentNode.appendChild(spot);
}

//positioning of the spot
spot.style.left = e.pageX || (event.clientX + document.body.scrollLeft);
spot.style.top = e.pageY || (event.clientY + document.body.scrollTop);
}

Now, about the CAPTCHA image creation. In the loaded template picture, a rectangle with randomly defined coordinates is selected. Then, the coordinates are saved in the Session. It is possible to store them in the ViewState, but in this case, they have to be encoded because the ViewState is accessible on the client side. Then, the image in the rectangle boundaries is stretched (any other distortion may be used, the only condition - it has to be notable for the site visitor).

Also, here is another problem. It is possible to compare the template (original) image and the final distorted image pixel by pixel in order to find the distorted area. To avoid this possibility, the template image is also changed in a random way. In the example below, it is stretched or compressed on a random scale.
//template image loading
using (System.Drawing.Image img = System.Drawing.Image.FromFile(this.Page.MapPath(TemplateImageUrl)))
{
//CAPCTHA image size is smaller then the template image one.
int captchaWidth = (int)(img.Width * 0.9);
int captchaHeight = (int)(img.Height * 0.9);
bitmap = new Bitmap(captchaWidth, captchaHeight);
Graphics g = Graphics.FromImage(bitmap);
//the rectangle dimension
int rectWidth = 20;
int rectHeight = 20;
Random r = new Random();
//the original (template) image is stretched or shrunken in a random way
float scaleX = 1 + r.Next(-100, 100) / 1000f;
float scaleY = 1 + r.Next(-100, 100) / 1000f;
g.ScaleTransform(scaleX, scaleY);
g.DrawImage(img, 0, 0, img.Width, img.Height);
g.ResetTransform();

//the rectangle coordinates are selected in a random way too
int x = r.Next(0, captchaWidth - rectWidth);
int y = r.Next(0, captchaHeight - rectHeight);
Rectangle distortedRect = new Rectangle(x, y, rectWidth, rectHeight);
HttpContext.Current.Session["ImgCAPTCHA.coords"] = distortedRect;
rectWidth = rectWidth + 10;
rectHeight = rectHeight + 10;
if (x + rectWidth > captchaWidth)
x = captchaWidth - rectWidth;
if (y + rectHeight > captchaHeight)
y = captchaHeight - rectHeight;
//draw distorted part of the image
g.DrawImage(img, distortedRect, new Rectangle(x, y, rectWidth, rectHeight), GraphicsUnit.Pixel);
g.DrawRectangle(Pens.Black, 0, 0, captchaWidth-1, captchaHeight-1);
}

Maybe the proposed image creation algorithm looks imperfect. Indeed, it is. I want to say that this article is rather the idea presentation then the control's description (it is just an example).

How to use

In order to use the control it has to assign the path of the template image - TemplateImageUrl property. The result of the CAPTCHA control action is a value of IsValid property. Besides here is the ability to lock user after the fixed number of failed attempts per session (FailedAttemptsBeforeLocking, IsLocked).

Download code - 34Kb

Update: I left the demo project for the demonstration how to implement this kind of CAPTCHAs. It is functional but not too optimal. Do not judge this code harshly :), it was written offhand specifically to illustrate this article that was published May 20, 2007. The control was greatly changed since then. If you want to include such CAPTCHA in your project then better to use ready Image-Based Bot Detector control that is available for download since Jun 24, 2007. It is free, with advanced facilities, tested in the most of modern browsers.

Image-based CAPTCHA

The brief overview of the most known implementations

  • Carnegie Mellon's PIX CAPTCHA - the so called "naming images CAPTCHA" - the user sees a few pictures, and has to select a word that is appropriate to all the shown pictures. The main problem of this type of CAPTCHAs is misspelling while writing the answer, and synonyms for the answer-word (for example: dog, hound, pooch). In the described case, this is solved by means of transferring all the variants of the answer to the client side.
  • Oli Warner's KittenAuth - in order to prove his humanity, the visitor has to select all animals of a specified species among the proposed pictures. But, the limited number of pictures allows to recreate the picture base manually.
  • Microsoft's Asirra - in outline, it is similar to KittenAuth - the user has to distinguish cats from dogs - but, it works with an extremely large array of pictures (photos of homeless animals from the specialized site), and a reconstruction of the picture base is impossible.
  • IMAGINATION - a CAPTCHA that requires two steps to be passed. At the first step, the visitor clicks elsewhere on the picture that is composed of a few images, and selects a single image. In the second step, the selected image is loaded. It is enlarged, but very distorted. Also, variants of the answer are loaded on the client side. The visitor should select a correct answer from the set of proposed words.

Why are the image-based CAPTCHAs NOT so widespread as the text-based ones?

I will not touch on the contrastive analysis of the possibility to crack them, you can find some thoughts/calculations here and here. I want to express my point of view as a web developer. So, why?
  • They are too large. A CAPTCHA should not take a dominant position on a web page. Is is only an ancillary element that serves to weed out bots under forms filling, getting some information, etc.
  • The traffic. A few pictures, each with a size of about 5-10 KB, would weigh a lot for a single page, in my opinion. Visitors using a low bandwidth network will be unpleasantly impressed, not to mention visitors using dial-up connections.
  • The inconsistency to the general conception of a website. A CAPTCHA with cats (or dolphins) will be appropriate on a leisure site, but will be irrelevant - for example - on a site of a medical institution. In this case, it is possible to gather a number of images of medical subjects, but - on the analogy of Asirra - it would be problematic to find a site with a large amount of photos of homeless doctors :).
  • The laborious process of the picture base creation.

I have to note that it is not a criticism in any way - I only want to find an answer for the above question. Let's sum up the aforesaid. An image-based CAPTCHA might be a good alternative to a text-based one if it would be a single, light-weight image based on a limited set of pictures.

The idea

Look at these two pictures:

It is easy to notice that the right image is slightly distorted, and it is not hard to outline a rough region where the distortion takes place. In order to notice it, the original image is not required. A human easy copes with this task even he sees an image the first time and does not know what the image depicts - the aforesaid does not apply to an expressionist's pictures :).

Now about bots. I have never worked with image recognition systems and my knowledge in this area is rather poor. Perhaps, the proposed variant is intricate to parse by special programs, perhaps not - it will be interesting to hear an expert's opinion.

Demo project (C#, ASP.Net) that illustrates the above conception of the image-based CAPTCHA.