|
/* This class has been written by
* Corinna John (Hannover, Germany)
* cj@binary-universe.net
*
* You may do with this code whatever you like,
* except selling it or claiming any rights/ownership.
*
* Please send me a little feedback about what you're
* using this code for and what changes you'd like to
* see in later versions. (And please excuse my bad english.)
*
* WARNING: This is experimental code.
* Exception handling has been omitted,
* to keep the code readable to people who want
* to understand the algorithm.
* Please do not expect "Release Quality".
* */
#region Using directives
using System;
using System.IO;
using System.Data;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
#endregion
namespace SteganoList {
public class HtmlUtility {
public HtmlUtility() {}
/// <summary>Counts the attributes in an HTML document</summary>
/// <param name="sourceFileName">Path and name of the HTML document</param>
/// <returns>Count of bytes that can be hidden in the document</returns>
public int GetCapacity(String sourceFileName) {
int countCarrierAttributes = 0;
StreamReader reader = new StreamReader(sourceFileName, Encoding.Default);
String htmlDocument = reader.ReadToEnd();
reader.Close();
HtmlTagCollection tags = FindTags(htmlDocument);
foreach (HtmlTag tag in tags) {
countCarrierAttributes += tag.Attributes.Count - 1;
}
return countCarrierAttributes;
}
/// <summary>Hide a message in an HTML document</summary>
/// <param name="sourceFileName">Path and name of the HTML document</param>
/// <param name="destinationFileName">Path and name to save the resulting HTML document</param>
/// <param name="message">The message to hide</param>
/// <param name="alphabet">Custom alphabet or empty String</param>
public void Hide(String sourceFileName, String destinationFileName, Stream message, String alphabet) {
//read the carrier document
StreamReader reader = new StreamReader(sourceFileName, Encoding.Default);
String htmlDocument = reader.ReadToEnd();
reader.Close();
message.Position = 0;
//list the HTML tags
HtmlTagCollection tags = FindTags(htmlDocument);
StringBuilder insertTextBuilder = new StringBuilder();
int offset = 0;
int messageByte = 0;
int bitIndex = -1;
bool messageBit = false;
int listElementIndex = 0;
Random random = new Random();
foreach (HtmlTag tag in tags) {
insertTextBuilder.Remove(0, insertTextBuilder.Length);
insertTextBuilder.AppendFormat("<{0}", tag.Name);
//list attribute names
String[] attributeNames = new String[tag.Attributes.Count];
for (int n = 0; n < attributeNames.Length; n++) {
attributeNames[n] = tag.Attributes[n].Name;
}
StringCollection resultList = new StringCollection();
StringCollection originalList = Utilities.SortLines(attributeNames, alphabet);
if (tag.Attributes.Count > 1) {
//sort attributes
for (int n = 0; n < attributeNames.Length - 1; n++) {
//get next bit of the message
bitIndex++;
if (bitIndex == 8) {
bitIndex = 0;
if (messageByte > -1) {
messageByte = message.ReadByte();
}
}
if (messageByte > -1) {
//decide which attribute is going to be the next one in the new tag
messageBit = ((messageByte & (1 << bitIndex)) > 0) ? true : false;
if (messageBit) {
listElementIndex = 0;
} else {
listElementIndex = random.Next(1, originalList.Count);
}
} else {
listElementIndex = 0;
}
//move the attribute from old list to new list
resultList.Add(originalList[listElementIndex]);
originalList.RemoveAt(listElementIndex);
}
}
if (originalList.Count > 0) {
//add the last element - it never hides data
resultList.Add(originalList[0]);
}
HtmlTag.HtmlAttribute attribute;
foreach (String attributeName in resultList) {
attribute = tag.Attributes.GetByName(attributeName);
insertTextBuilder.Append(" ");
if (attribute.Value.Length > 0) {
insertTextBuilder.AppendFormat("{0}={1}", attribute.Name, attribute.Value);
} else {
insertTextBuilder.Append(attributeName);
}
}
//replace old tag with new tag
tag.BeginPosition += offset;
tag.EndPosition += offset;
String insertText = insertTextBuilder.ToString();
int newLength = insertText.Length;
if (newLength > 0) {
int oldLength = tag.EndPosition - tag.BeginPosition;
htmlDocument = htmlDocument.Remove(tag.BeginPosition, oldLength);
htmlDocument = htmlDocument.Insert(tag.BeginPosition, insertText);
offset += (newLength - oldLength);
}
if (messageByte < 0) {
break; //finished
}
}
//save the new document
StreamWriter writer = new StreamWriter(destinationFileName);
writer.Write(htmlDocument);
writer.Close();
}
/// <summary>Extract a hidden message from an HTML document</summary>
/// <param name="sourceFileName">Path and name of the HTML document</param>
/// <param name="message">Empty stream for the message</param>
/// <param name="alphabet">Custom alphabet or empty String</param>
public Stream Extract(String sourceFileName, String alphabet)
{
//initialize empty writer for the message
BinaryWriter messageWriter = new BinaryWriter(new MemoryStream());
//read the carrier document
StreamReader reader = new StreamReader(sourceFileName, Encoding.Default);
String htmlDocument = reader.ReadToEnd();
reader.Close();
//list the HTML tags
HtmlTagCollection tags = FindTags(htmlDocument);
int messageLength = 0;
int messageBit = 0;
int bitIndex = 0;
int messageByte = 0;
foreach (HtmlTag tag in tags) {
if (tag.Attributes.Count > 1) {
//list attribute names
String[] attributeNames = new String[tag.Attributes.Count];
for (int n = 0; n < attributeNames.Length; n++) {
attributeNames[n] = tag.Attributes[n].Name;
}
StringCollection carrierList = new StringCollection();
carrierList.AddRange(attributeNames);
carrierList.RemoveAt(carrierList.Count - 1);
//sort -> get original list
StringCollection originalList = Utilities.SortLines(attributeNames, alphabet);
String[] unchangeableOriginalList = new String[originalList.Count];
originalList.CopyTo(unchangeableOriginalList, 0);
foreach (String s in carrierList) {
//decide which bit the entry's position hides
if (s == originalList[0]) {
messageBit = 1;
} else {
messageBit = 0;
}
//remove the color from the sorted list
originalList.Remove(s);
//add the bit to the message
messageByte += (byte)(messageBit << bitIndex);
bitIndex++;
if (bitIndex > 7) {
if (messageLength == 0) {
messageLength = messageByte;
} else {
//append the byte to the message
messageWriter.Write((byte)messageByte);
if (messageWriter.BaseStream.Length == messageLength) {
//finished
break;
}
}
messageByte = 0;
bitIndex = 0;
}
}
}
if ((messageLength > 0) && (messageWriter.BaseStream.Length == messageLength)) {
//finished
break;
}
}
//return message stream
messageWriter.Seek(0, SeekOrigin.Begin);
return messageWriter.BaseStream;
}
/// <summary>List all HTML tags of a document</summary>
/// <param name="htmlDocument"></param>
/// <returns>List with</returns>
private HtmlTagCollection FindTags(String htmlDocument) {
HtmlTagCollection tags = new HtmlTagCollection();
int indexStart = 0, indexEnd = 0;
String text;
do {
indexStart = htmlDocument.IndexOf('<', indexEnd + 1);
if (indexStart > 0) {
indexEnd = htmlDocument.IndexOf('>', indexStart + 1);
if (indexEnd > 0) {
if (htmlDocument[indexStart + 1] != '/') {
//Ende vom Start-Tag gefunden
text = htmlDocument.Substring(indexStart, indexEnd - indexStart);
tags.Add(new HtmlTag(text, indexStart, indexEnd));
}
}
}
} while (indexStart > 0);
return tags;
}
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.