Creating a Link Anonymiser Service for Analog CE’s ANONYMIZERURL setting

This article discusses how to create an link anonymiser service redirector to make use of the Analog CE 6.0.16+ ANONYMIZERURL setting.


Why use an anonymiser?

If a user clicks a link on an Analog CE “Requesting Site” or “Requesting URL” report. The users web browser will send a HTTP Referrer header with the request to download the web page; this request will include the full URL or your Analog CE report. The receiving server will likely log the request, allowing its owner to see where the request originated.

This may expose the Internet or Intranet URL or your stats page to the target website owner. They may in-turn inadvertantly publicise it via their own statistics page and/or link-back tracker service. This makes it possible for other agents, including competitors, search engines and malicious users to discover information about your website. Worse your web server may become the target of SEO spammers.


What is SEO spam?

SEO spam is the practice of attempting to improve a website/page position on a search engine by creating ‘false’ links into that website. If your referring site/URL report is public it is possible for a malicious actor to artificially position one or more URLs on the report. This is achieved through a manipulated HTTP GET request containing a HTTP Referrer header with the URL/site that they want to inject onto your report. After making several hundred requests in this fashion the spammer will wait for the report to be updated. After confirming that their site has appeared in the report, they submit your statistics page(s) to search engines.

Once compromised, it is likely that your exploitability will be recorded in one or more botnets and will see wider exploitation.


Why create your own anonymiser?

You can use public anonymiser services such as or with Analog CE using one of the code samples below.



This may not be acceptable to you, or your organisational security policy. Firstly because while the owner of the resultant web server will not discover the true origin of the request, the public anonymiser service will. Secondly, there is no contract assuring service availability or the privacy of its log files. Finally, it is inevitable that the service is going to profit from your transaction. Advertising placement is likely, creating a delay in the redirect.


Code your own Anonymous Link Redirector

The following code snippets can be used to program your own basic redirector using service side scripting technology.


ASP 3 / Classic ASP

<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001" EnableSessionState="False"%>
<% Option Explicit %>
  Response.Status = "302 Found"
  call Response.AddHeader("Location", Request.QueryString)

Save the file as redirector.asp and add the following to your Analog CE global configuration file:


<%@ Page Language="C#" %>
<script runat="server">
  private void Page_Load(object sender, EventArgs e)
    Response.Redirect(HttpContext.Current.Request.ServerVariables["QUERY_STRING"], true);

Save the file as redirector.aspx and add the following to your Analog CE global configuration file:




  header('Location: ' . $_SERVER['QUERY_STRING'], true, 302);

Save the file as redirector.php and add the following to your Analog CE global configuration file:




The above code samples illustrate how to create a redirector in several different languages. The redirector URL will be sent to the destination server however the originating statistics page will now be protected. This protects your Analog CE stats pages from prying eyes while reducing the risk of SEO spamming.

VBScript Timer() function precision

This article explores the numeric precision of the ASP and VBScript Timer() function, outlining that it may be more accurate than it initially appears.



Prior to the arrival of PowerShell. The VBScript Timer() function wass the closest thing that script creators had for undertaking high precision timings in the Windows Scripting Host (WSH) environment.

The Timer() function returns a VBSingle – aka a Single Prevision floating point number or a “Real” – value as a representation of the systems real time clock. As a Single precision value, the permitted range is -3.402823E38 to -1.401298E-45 for negative values; 1.401298E-45 to 3.402823E38 for positive values. If you print out the value of Timer() in ASP/VBScript

WScript.Echo Timer()

You will get a value like


This value is a representation of the number of seconds that have elapsed on the local executing system since the local system click last hit midnight. Consequently, evaluating Timer() against a DateDiff() evaluation of the number of seconds since Midnight will result in the same answer (save for the decimal point).

WScript.Echo Timer()
WScript.Echo DateDiff("s", #2019-07-01#, Now())

Which results in



The hidden detail

Masked in the 2 decimal places default printout. Timer() is significantly more detailed than just two decimal points. Depending on the precision of your systems Real Time Clock (RTC) the precision may be up to 7 decimal places. You can view your systems capability by subtracting the Integer from the timer value:

WScript.Echo Timer() - Int(Timer())

Which may result in a value such as


At 7 decimal places, the precision of Timer() is – floating point number inaccuracy side – considerably better than that of VBDateTime. Under VBDateTime, the second is the atomic value, offering no more precision.

0         - Second
0.6       - Decisecond  / 1 tenth
0.61      - Centisecond / 1 hundredth
0.617     - Millisecond / 1 thousandth
0.6171    -               1 ten thousandth
0.61718   -               1 hundred thousandth
0.617187  - Microsecond / 1 millionth
0.6171875 -               1 ten millionth

This demonstrates that there is flexibility in VBScript for more precise clock operations. But is the resolution high enough?



The resolution and fidelity of the Timer() function is what makes it valuable (or not) to a programmer. On a modern system, with a High Precision Event Timer (HPET), the update interrupt will only fire so many times to update the clock.

Running an imprecise test as follows:

for i = 0 to 999
  WScript.Echo Timer() - Int(Timer())

The output value only changed between 10 and 31 cycles (reflective of the CPU scheduler performing other tasks during execution. The counter incrementation was consistent, updating 48 times with an increment of between 0.0117187 and 0.0195313 seconds. Once every 20.8 cycles on average.


In practice this means that the viable, comparable resolution of Timer() is not much better than once aver 0.2 seconds. Over a longer time period, Timer() can offer higher accuracy: provided you aren’t polling for an update more than every 0.2 of a second.

Replace line breaks in PRE tags using RegEx in ASP/VBScript

This article discusses how to replace unnecessary line breaks (<br>, <br />) in HTML pre-formatted ‘PRE‘ tags using RegEx and ASP/VBScript.

The Problem

When I added a [code] tag to the HPC:Factor Community Forums markup some years ago. There was an obvious, but low-priority problem that had niggled at me. The BBS tag renders a pre-formatted ‘PRE‘ tag into the post HTML and styles it using a console font and fixed-size characters for improved legibility. The way that MegaBBS works however is that new lines (vbcrlf, vblf, \r\n, \n, chr(13) & chr(10), chr(10)) are replaced with ‘<br />‘ as a batch replace at the beginning of the forum sanitisation and rendering process.

This means that when the browser renders the <pre></pre> tag, it renders both the vblf and the <br />, leading to double line breaks.

For example

Dim i
i = 0
while (i < 1000)
  Response.Write i
  i = (i + 1)


<pre><br />
Dim i<br />
i = 0<br />
while (i < 1000)<br />
  Response.Write i<br />
  i = (i + 1)<br />
loop<br />
</pre><br />

leading the browser to render

Dim i

i = 0

while (i < 1000)

Response.Write i

i = (i + 1)


This wastes screen space and reduces legibility.

RegEx Fix

The solution is very simple, use RegEx to re-parse the [code] block after it has globally replaced the line breaks.

I added the following code at the bottom of the MBBS Code loop in the MBBSDecode function  in include.asp

if (vBBSDecodeArray(0, index) = "\[code\]") then	' This looks for the PRE tag for the code and then removes the <br />'s from it to return it to pure pre-formatted
	mBBSRegEx.pattern = "\<pre[^>]*\>((.|\n)*)\<\/pre\>"
	for each sNewText in mBBSRegEx.execute(MBBSDecode)
		MBBSDecode = Replace(MBBSDecode, sNewText.Value, (Replace(sNewText.Value, "<br />", "")))
end if

To evaluate what this means line-by-line

  1. When it is parsing the [code] tag from the list of all BBS markup statements
  2. Set a RegEx pattern to search for the opening PRE tag with zero or more attributes e.g. <pre attribute1="one" attribute2="two"> ending with </pre>. The “((.|\n)*)” ensures that the search looks for all characters, including over new lines, for as many characters and new lines as is necessary to encounter the closing </pre> tag.
  3. For every positive match i.e. for every <pre>*</pre> match
  4.  In the matches string, replace <br /> with "", then replace the match in the original source string (MBBSDecode) with the fixed string
  5. Move to the next match until there are no more matches


To genericise the example

Dim strHtml
Dim match
Dim matches
Dim regEx

strHtml = "<body><p>hello</p><pre class="">line one" & vbcrlf & "<br />line two" & vbcrlf & "<br />line three" & vbcrlf & "<br /></pre><p>hello</p></body>"

set regEx = New RegExp
    regEx.Pattern = "\<pre[^>]*\>((.|\n)*)\<\/pre\>"
    regEx.IgnoreCase = true
    regEx.Global = true
set matches = regEx.Execute(strHtml)

For Each match in matches
    strHtml =  = Replace(strHtml, match.Value, (Replace(match.Value, "<br />", "")))

' strHtml will now effectively be:
' "<body><p>hello</p><pre class="">line one" & vbcrlf & "line two" & vbcrlf & "line three" & vbcrlf & "</pre><p>hello</p></body>"

Fairly simple, but as with most things RegEx, a headache for most of us – unless you are using it all the time.

Exploration of which request headers you need, should and cannot set when using MsXml2.XmlHttp, Microsoft.XmlHttp and MsXml2.ServerXmlHttp

System Requirements:

  • Windows NT 4.0 Server SP4+
  • Windows Server 2000
  • Windows Server 2003
  • Windows Server 2008, R2
  • Windows Server 2012, R2
  • Windows 2000 Professional
  • Windows XP
  • Windows Vista
  • Windows 7
  • Windows 8, 8.1
  • IIS 4.0, 5.0, 5.1, 6.0, 7.0, 7.5, 8.0
  • ASP 3.0 (Classic)
  • VBScript

The Problem:

I was browsing around the web earlier looking for inspiration on making PowerShell send asynchronous XmlHttp requests when I came across a block of VBScript examples. In them, I observed that the coder was religiously attempting to set the Content-Length request header, something that when using MsXml I don’t usually do – it’s a different story with PHP.

Equally, I observed that the programmer was using Len() to define the length of the content body for text, but ASP usually operates using an internal UTF-16 encoding to represent strings, thus does it follow that Len() will give the correct value?

So I thought that I would explore the issue to see for my own amusement, what was actually going on.

More Info

Let us take a standard code block to use for our tests and use the services of the good people at for a public message echo system.

Option Explicit
Dim x
Dim strRequest : strRequest = "a=1&b=2&c=3" ' <- 11 charactersset x = CreateObject("MsXml2.XmlHttp")
call"POST", "", false)
call x.setRequestHeader("Content-Length", 11)
call x.setRequestHeader("Accept-Language", "EN-GB")
call x.setRequestHeader("Connection", "keep-alive")x.send(strRequest)

The above gives us a boiler plate for what we believe the correct header should looklike written in VBScript. When executed, HttpBin responds with:

"args": {},
"data": "a=1&b=2&c=3",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "EN-GB",
"Cache-Control": "no-cache",
"Connection": "close",
"Content-Length": "11",
"Host": "",
"User-Agent": "<omitted>",
"X-Request-Id": "<omitted>"
"json": null,
"origin": "<ommitted>",
"url": ""

HttpBin provides us with an echo of the header and WireShark provides us with a wire capture of the transport packet being sent between the XmlHttpResponse provider and the HttpBin server. With these two tools we can analyse the header states.


If you take out the line call x.setRequestHeader(“Content-Length”, 11) and do not specify a content-length, both HttpBin and Wireshark’s trace of the connection report Content-Length: 11\r\n, which is correct. So logically, this has been inserted by the XmlHttp parser during the request to .send().

So what happens if we attempt to override it. If we inject call x.setRequestHeader(“Content-Length”, 15), which is 4 characters too long, again both WireShark and HttpBin report

Content-Length: 11\r\n

The same occurs if we under-report the content length, i.e. 10 characters. Therefore it is safe to conclude that MsXml2.XmlHttp’s send() operator handles this for you and any processing of the Len() of the message body before sending is just wasting CPU time, memory and lines of code as it is ultimately an action that will be duplicated.

To further test the theory to see if it is required in down-level versions of MsXml, I repeated the experiment using HttpBin and Wireshark with the following COM objects:

MsXml2.XmlHttp            <-which is logically v3.0
MsXml2.ServerXmlHttp      <-which is logically v3.0

None of them could be coerced to include an incorrectly sized Content-Length and all of them appended the correct 11 character content length on their own when no attempt was made to manually provide it.

We can therefore categorically conclude that with MSXML 3.0 and higher, you do not need to waste time calculating the content length.

A an aside, when dealing with character data, the use of Len() appears to be correct for UTF-8 encoding. LenB() should be used when passing in binary data i.e. ADODB.Stream data, FileSystemObject data etc.


If you do not manually specify a Content-Type header, all MSXML providers appear to transmit POST encoded data as

Content-Type: text/xml; charset="utf-8"\r\n

As we were sending POST data (i.e. a HTML form), this is incorrect as the content-type should be “application/x-www-form-urlencoded; Charset=UTF-8”. Thus is is appropriate and necessary to include the Content-Type header manually as shown below.

call x.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; Charset=UTF-8")


Accept language was decidedly different depending on which provider you are using. You can safely override Accept-language by manually specifying it using setRequestHeader(“Accept-Language”, “EN-GB”).

Microsoft.XmlHttp         <- NOT SET
MsXml2.XmlHttp.3.0        <- NOT SET
MsXml2.XmlHttp.6.0        <- Derived it from OS i.e. Accept-Language: en-gb\r\n
MsXml2.ServerXmlHttp.3.0  <- Derived it from OS i.e. Accept-Language: en-gb\r\n
MsXml2.ServerXmlHttp.6.0  <- Derived it from OS i.e. Accept-Language: en-gb\r\n

Thus, if you are using the legacy Microsoft.XmlHttp or the client MsXml2.XmlHttp.3.0, it is necessary to manually enter the Accept-Language if of course it is needed.


All examples set the connection value to Connection: Keep-Alive\r\n when communicating with the HTTP/1.1 protocol. If you need to force the remote server to close the TCP connection immediately after the response has been received, you must manually set Connection: Close\r\n using setRequestHeader(“Connection”, “Close”).


Cache-control is only set by the client providers and can be added manually to the server providers.

Microsoft.XmlHttp         <- Cache-Control: no-cache\r\n
MsXml2.XmlHttp.3.0        <- Cache-Control: no-cache\r\n
MsXml2.XmlHttp.6.0        <- Cache-Control: no-cache\r\n
MsXml2.ServerXmlHttp.3.0  <- NOT SET
MsXml2.ServerXmlHttp.6.0  <- NOT SET


Accept-encoding is only set by the client providers and can be added manually to the server providers.

Microsoft.XmlHttp         <- Accept-Encoding: gzip, deflate\r\n
MsXml2.XmlHttp.3.0        <- Accept-Encoding: gzip, deflate\r\n
MsXml2.XmlHttp.6.0        <- Accept-Encoding: gzip, deflate\r\n
MsXml2.ServerXmlHttp.3.0  <- NOT SET
MsXml2.ServerXmlHttp.6.0  <- NOT SET


Accept is set to Accept: */*\r\n by all providers and can be overridden if required.