cshttp Tutorial - Day 6: Safety First (HTML Encoding)
Day 6: Safety First (HTML Encoding)
In Day 5, we built a live Community Guestbook where anyone can post a comment and everyone else can see it.
That is exciting... but there is a hidden danger in our Day 5 code! Right now, our server takes whatever text a visitor types into a form or URL and puts it directly into our HTML page.
Remember: HTML is not just plain text — it is code that the browser executes!
In Day 6, we will:
- Break our own website on purpose to see why trusting raw user input is dangerous (HTML Injection & XSS).
- Understand how
WebApp.HtmlEncode()neutralizes dangerous characters (<,>,",&). - Fix our
HandleGreet(),HandleGuestbook(), andHandleContact()methods step by step so our website is completely safe.
1. Let's Break Our Website on Purpose!
Before we write any new code, let's see the problem with our own eyes.
Run your Day 5 program (dotnet run or press Start in Visual Studio) and open http://localhost:8080/guestbook.
Experiment 1: Injecting HTML Tags
In the Guestbook form, type your name and post a comment with this exact text:
<b>I am BOLD!</b>
Look closely at the result in the guestbook list:
- Your comment is displayed as I am BOLD!
- The
<b>and</b>tags are invisible — because the browser thought your comment was part of the website's HTML code and ran it!
What if someone posted <h1>HUGE TEXT</h1> or <marquee>Moving text</marquee>? They could completely mess up the layout of your page for every visitor!
Experiment 2: Injecting JavaScript (<script>)
Now try something much more serious. Post this comment in the Guestbook:
<script>alert('Your website has been hacked!')</script>
As soon as the page reloads, a browser popup box appears on your screen! Even worse: because that comment is saved in guestbookEntries, that popup will now appear for every single person who visits your Guestbook!
In the cybersecurity world, this famous vulnerability is called Cross-Site Scripting (XSS). When a website lets a visitor inject <script> tags into a page, an attacker can run malicious JavaScript inside other visitors' browsers (for example, to steal login sessions or redirect visitors to a fake scam page).
Experiment 3: The Same Flaw on /greet
Our /greet and /contact pages have the exact same problem! Try visiting this URL in your browser:
http://localhost:8080/greet?name=<i>Hello</i>
The greeting appears in italics (Hello, Hello!), because {name} was inserted directly into the HTML without any protection.
2. How Do We Fix It? (WebApp.HtmlEncode)
Why did this happen? Because the browser cannot tell the difference between:
- Trusted HTML that you (the developer) wrote (
<h1>,<ul>,<li>), and - Untrusted text that a visitor typed into an input box.
Whenever the browser sees a < character, it assumes a new HTML tag is starting!
To stop the browser from treating visitor text as HTML tags, cshttp provides a built-in helper method:
string safeText = WebApp.HtmlEncode(rawText);
What Does WebApp.HtmlEncode() Do?
It scans the string and replaces special HTML characters with safe HTML Entities:
| If the visitor types | WebApp.HtmlEncode turns it into | What the browser displays on screen |
|---|---|---|
< | < | < (as harmless text!) |
> | > | > (as harmless text!) |
" | " | " |
' | ' | ' |
& | & | & |
For example, if a mischievous visitor types:
<script>alert('hack')</script>
WebApp.HtmlEncode() converts it into:
<script>alert('hack')</script>
When the browser receives <script>, it knows not to run it as code—instead, it harmlessly draws the literal characters <script> on the screen as plain text!
The Golden Rule of Web Security: Never trust user input! Every time you place text that came from a visitor (
Request.QueryStringorRequest.Form) into an HTML string, wrap it withWebApp.HtmlEncode(...).
3. Fixing /greet (HandleGreet)
Let's fix our three interactive pages one by one, starting with the simplest one: HandleGreet().
In Day 5, the else block inside HandleGreet() looked like this:
else
{
greetingMessage = $"<h2>Hello, {name}! Welcome to C# Web Development!</h2>";
}
Here, {name} comes directly from Request.QueryString["name"], which is untrusted user input.
Let's encode name into a new variable called safeName before putting it into greetingMessage:
else
{
// NEW IN DAY 6: Encode the name from the URL so HTML tags cannot run!
string safeName = WebApp.HtmlEncode(name);
greetingMessage = $"<h2>Hello, {safeName}! Welcome to C# Web Development!</h2>";
}
Now, if someone visits /greet?name=<i>Hello</i>, safeName becomes <i>Hello</i>, and the browser safely prints Hello, <i>Hello</i>! without turning it italic!
4. Fixing /guestbook (HandleGuestbook)
Next, let's look at HandleGuestbook() where we loop through guestbookEntries to build our <ul> list.
In Day 5, our foreach loop looked like this:
StringBuilder listHtml = new StringBuilder();
listHtml.Append("<ul>");
foreach (string entry in guestbookEntries)
{
listHtml.Append($"<li style='margin-bottom: 8px;'>{entry}</li>");
}
listHtml.Append("</ul>");
Notice that {entry} contains the visitor's name and comment, which was placed directly inside <li>...</li>.
Let's encode entry using WebApp.HtmlEncode(entry) before appending it into the <li> tag:
StringBuilder listHtml = new StringBuilder();
listHtml.Append("<ul>");
foreach (string entry in guestbookEntries)
{
// NEW IN DAY 6: Encode the entry before putting it into the HTML
string safeEntry = WebApp.HtmlEncode(entry);
listHtml.Append($"<li style='margin-bottom: 8px;'>{safeEntry}</li>");
}
listHtml.Append("</ul>");
Notice what we encoded—and what we did NOT encode! We only encoded
entry(the visitor's text). We did not encode<li style='margin-bottom: 8px;'>or</li>, because we wrote those<li>tags and we want the browser to render them as a bulleted list!
5. Fixing /contact (HandleContact)
Finally, let's check HandleContact(). When a visitor submits the Contact form (POST), we read three fields from Request.Form:
string senderName = Request.Form["sender"];
string email = Request.Form["email"];
string message = Request.Form["message"];
And then we display all three fields on the "Thank You" confirmation page!
Let's encode all three variables (safeName, safeEmail, safeMessage) before inserting them into thankYouContent:
if (Request.HttpMethod == "POST")
{
string senderName = Request.Form["sender"];
string email = Request.Form["email"];
string message = Request.Form["message"];
Console.WriteLine($"[New Message] From: {senderName} ({email}) - {message}");
// NEW IN DAY 6: Encode everything the visitor typed before putting it into HTML
string safeName = WebApp.HtmlEncode(senderName);
string safeEmail = WebApp.HtmlEncode(email);
string safeMessage = WebApp.HtmlEncode(message);
string thankYouContent = $@"
<h1>Thank You, {safeName}!</h1>
<p>We received your message and will reply to <b>{safeEmail}</b> soon.</p>
<div style='background: #f0f0f0; padding: 10px; border-radius: 4px;'>
<i>""{safeMessage}""</i>
</div>
<p><a href='/contact'>Send another message</a></p>";
RenderPage("Message Received", thankYouContent);
return;
}
(Note: We don't need to HTML-encode Console.WriteLine(...) because the console terminal is plain text, not a web browser running HTML!)
6. Complete Code for Day 6
Here is the complete Program.cs with all three pages protected by WebApp.HtmlEncode():
using System;
using System.Collections.Generic;
using System.Text;
using cshttp;
namespace ConsoleApp1
{
internal class Program
{
static List<string> guestbookEntries = new List<string>()
{
"[2026-10-22 09:00] System: Welcome to our new Guestbook!",
"[2026-10-22 09:05] Alice: First comment! C# web dev is awesome."
};
static void Main(string[] args)
{
WebApp.Run(8080, OnRequest);
}
static void OnRequest()
{
string path = Request.Path.ToLower();
switch (path)
{
case "/":
case "/home":
HandleHome();
break;
case "/guestbook":
HandleGuestbook();
break;
case "/greet":
HandleGreet();
break;
case "/about":
HandleAbout();
break;
case "/contact":
HandleContact();
break;
default:
HandleNotFound();
break;
}
}
static void RenderPage(string title, string bodyContent)
{
string html = $@"
<html>
<head>
<title>{title}</title>
</head>
<body>
<nav>
<a href='/'>Home</a> |
<a href='/guestbook'>Guestbook</a> |
<a href='/greet'>Greet</a> |
<a href='/about'>About</a> |
<a href='/contact'>Contact</a>
</nav>
<hr />
{bodyContent}
<hr />
<footer>
<small>Server Time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}</small>
</footer>
</body>
</html>";
Response.Write(html);
}
static void HandleGuestbook()
{
if (Request.HttpMethod == "POST")
{
string author = Request.Form["author"];
string comment = Request.Form["comment"];
if (!string.IsNullOrWhiteSpace(author) && !string.IsNullOrWhiteSpace(comment))
{
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm");
string entry = $"[{timestamp}] {author}: {comment}";
guestbookEntries.Add(entry);
}
Response.Redirect("/guestbook");
return;
}
StringBuilder listHtml = new StringBuilder();
listHtml.Append("<ul>");
foreach (string entry in guestbookEntries)
{
// NEW IN DAY 6: Encode the entry before putting it into the HTML
string safeEntry = WebApp.HtmlEncode(entry);
listHtml.Append($"<li style='margin-bottom: 8px;'>{safeEntry}</li>");
}
listHtml.Append("</ul>");
string content = $@"
<h1>Community Guestbook</h1>
<p>Leave a message for everyone visiting this site!</p>
<form action='/guestbook' method='post' style='background: #f9f9f9; padding: 15px; border: 1px solid #ddd; max-width: 450px;'>
<div>
<label>Your Name:</label><br />
<input type='text' name='author' required style='width: 100%;' />
</div>
<br />
<div>
<label>Your Comment:</label><br />
<textarea name='comment' rows='3' required style='width: 100%;'></textarea>
</div>
<br />
<button type='submit'>Sign Guestbook</button>
</form>
<hr />
<h2>Recent Entries ({guestbookEntries.Count})</h2>
{listHtml.ToString()}";
RenderPage("Guestbook", content);
}
static void HandleContact()
{
if (Request.HttpMethod == "POST")
{
string senderName = Request.Form["sender"];
string email = Request.Form["email"];
string message = Request.Form["message"];
Console.WriteLine($"[New Message] From: {senderName} ({email}) - {message}");
// NEW IN DAY 6: Encode everything the visitor typed
string safeName = WebApp.HtmlEncode(senderName);
string safeEmail = WebApp.HtmlEncode(email);
string safeMessage = WebApp.HtmlEncode(message);
string thankYouContent = $@"
<h1>Thank You, {safeName}!</h1>
<p>We received your message and will reply to <b>{safeEmail}</b> soon.</p>
<div style='background: #f0f0f0; padding: 10px; border-radius: 4px;'>
<i>""{safeMessage}""</i>
</div>
<p><a href='/contact'>Send another message</a></p>";
RenderPage("Message Received", thankYouContent);
return;
}
string formContent = @"
<h1>Contact Us</h1>
<p>Send us a message below:</p>
<form action='/contact' method='post'>
<div>
<label>Your Name:</label><br />
<input type='text' name='sender' required style='width: 300px;' />
</div>
<br />
<div>
<label>Email Address:</label><br />
<input type='email' name='email' required style='width: 300px;' />
</div>
<br />
<div>
<label>Message:</label><br />
<textarea name='message' rows='5' cols='40' required></textarea>
</div>
<br />
<button type='submit'>Send Message</button>
</form>";
RenderPage("Contact", formContent);
}
static void HandleGreet()
{
string name = Request.QueryString["name"];
string greetingMessage = "";
if (string.IsNullOrEmpty(name))
{
greetingMessage = "<p>Please enter your name below, or try clicking a quick link!</p>";
}
else
{
// NEW IN DAY 6: Encode the name from the URL
string safeName = WebApp.HtmlEncode(name);
greetingMessage = $"<h2>Hello, {safeName}! Welcome to C# Web Development!</h2>";
}
string content = $@"
<h1>Interactive Greeting</h1>
{greetingMessage}
<form action='/greet' method='get'>
<label>Your Name:</label>
<input type='text' name='name' placeholder='Type your name...' />
<button type='submit'>Say Hello</button>
</form>
<p>Or test query links directly:
<a href='/greet?name=Alice'>Greet Alice</a> |
<a href='/greet?name=Bob'>Greet Bob</a>
</p>";
RenderPage("Greet", content);
}
static void HandleHome()
{
string content = @"
<h1>Welcome cshttp!</h1>
<p>Our <a href='/guestbook'>Guestbook</a> is now safe from HTML injection. Try to break it!</p>";
RenderPage("Home", content);
}
static void HandleAbout()
{
string content = @"
<h1>About Us</h1>
<p>It's Me!</p>";
RenderPage("About Us", content);
}
static void HandleNotFound()
{
Response.StatusCode = 404;
string content = @"
<h1>404 Not Found</h1>
<p>The page that you are looking for does not exist.</p>";
RenderPage("404 Not Found", content);
}
}
}
7. Test It in Your Browser
- Stop the old server and run the updated Day 6 code (
dotnet runor press Start in Visual Studio). - Open
http://localhost:8080/guestbook. - Try our attacks again! Post
<b>I am BOLD!</b>and<script>alert('hacked')</script>. - Look at the result:
- Nothing turns bold, and no popup box appears!
- Instead, the literal characters
<b>I am BOLD!</b>and<script>alert('hacked')</script>are safely displayed on the screen as plain text.
- Right-click anywhere on the web page and select View Page Source. Look at your
<li>items in the HTML source code: you will see<b>and<script>—that isWebApp.HtmlEncode()doing its job! - Visit
http://localhost:8080/greet?name=<i>Hello</i>—the greeting now safely prints<i>Hello</i>without turning italic.
In Day 7, we will upgrade our Guestbook entries from a single glued-together string into a structured C# class (GuestbookEntry) so we can store and style the author's name, timestamp, and message separately!