Monday, December 01, 2008

Version of System.Web.HttpUtility for .NET Client Profile

The .NET Framework Client Profile released with .NET 3.5 SP1 defines a stripped-down version of the .NET Framework for distribution with rich-client applications. This is a valuable feature for developers who need to keep their software distributions small and convenient to install. (For more information, see my previous post comparing bootstrap install times for various .NET framework versions.)

The Client Profile originally included the System.Web assembly, but this was removed from the final service pack release. This is unfortunate because System.Web includes several utility operations that are useful in rich client applications. One of these is URL encoding and decoding. It would be easy enough to code up a replacement function, but why bother? Instead I've decompiled the latest version of System.Web.HttpUtility (v2.0.50727) and stripped out the functions that depend on System.Web. All of the URL encoding methods are intact. The code is below--use at your own risk.

   1: using System.Text;
   2:  
   3: namespace System.Web
   4: {
   5:     public sealed class HttpUtility
   6:     {
   7:         private static int HexToInt(char h)
   8:         {
   9:             if ((h >= '0') && (h <= '9'))
  10:             {
  11:                 return (h - '0');
  12:             }
  13:             if ((h >= 'a') && (h <= 'f'))
  14:             {
  15:                 return ((h - 'a') + 10);
  16:             }
  17:             if ((h >= 'A') && (h <= 'F'))
  18:             {
  19:                 return ((h - 'A') + 10);
  20:             }
  21:             return -1;
  22:         }
  23:  
  24:         internal static char IntToHex(int n)
  25:         {
  26:             if (n <= 9)
  27:             {
  28:                 return (char) (n + 0x30);
  29:             }
  30:             return (char) ((n - 10) + 0x61);
  31:         }
  32:  
  33:         private static bool IsNonAsciiByte(byte b)
  34:         {
  35:             if (b < 0x7f)
  36:             {
  37:                 return (b < 0x20);
  38:             }
  39:             return true;
  40:         }
  41:  
  42:         internal static bool IsSafe(char ch)
  43:         {
  44:             if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) || ((ch >= '0') && (ch <= '9')))
  45:             {
  46:                 return true;
  47:             }
  48:             switch (ch)
  49:             {
  50:                 case '\'':
  51:                 case '(':
  52:                 case ')':
  53:                 case '*':
  54:                 case '-':
  55:                 case '.':
  56:                 case '_':
  57:                 case '!':
  58:                     return true;
  59:             }
  60:             return false;
  61:         }
  62:  
  63:         public static string UrlDecode(string str)
  64:         {
  65:             if (str == null)
  66:             {
  67:                 return null;
  68:             }
  69:             return UrlDecode(str, Encoding.UTF8);
  70:         }
  71:  
  72:         public static string UrlDecode(byte[] bytes, Encoding e)
  73:         {
  74:             if (bytes == null)
  75:             {
  76:                 return null;
  77:             }
  78:             return UrlDecode(bytes, 0, bytes.Length, e);
  79:         }
  80:  
  81:         public static string UrlDecode(string str, Encoding e)
  82:         {
  83:             if (str == null)
  84:             {
  85:                 return null;
  86:             }
  87:             return UrlDecodeStringFromStringInternal(str, e);
  88:         }
  89:  
  90:         public static string UrlDecode(byte[] bytes, int offset, int count, Encoding e)
  91:         {
  92:             if ((bytes == null) && (count == 0))
  93:             {
  94:                 return null;
  95:             }
  96:             if (bytes == null)
  97:             {
  98:                 throw new ArgumentNullException("bytes");
  99:             }
 100:             if ((offset < 0) || (offset > bytes.Length))
 101:             {
 102:                 throw new ArgumentOutOfRangeException("offset");
 103:             }
 104:             if ((count < 0) || ((offset + count) > bytes.Length))
 105:             {
 106:                 throw new ArgumentOutOfRangeException("count");
 107:             }
 108:             return UrlDecodeStringFromBytesInternal(bytes, offset, count, e);
 109:         }
 110:  
 111:         private static byte[] UrlDecodeBytesFromBytesInternal(byte[] buf, int offset, int count)
 112:         {
 113:             int length = 0;
 114:             byte[] sourceArray = new byte[count];
 115:             for (int i = 0; i < count; i++)
 116:             {
 117:                 int index = offset + i;
 118:                 byte num4 = buf[index];
 119:                 if (num4 == 0x2b)
 120:                 {
 121:                     num4 = 0x20;
 122:                 }
 123:                 else if ((num4 == 0x25) && (i < (count - 2)))
 124:                 {
 125:                     int num5 = HexToInt((char) buf[index + 1]);
 126:                     int num6 = HexToInt((char) buf[index + 2]);
 127:                     if ((num5 >= 0) && (num6 >= 0))
 128:                     {
 129:                         num4 = (byte) ((num5 << 4) | num6);
 130:                         i += 2;
 131:                     }
 132:                 }
 133:                 sourceArray[length++] = num4;
 134:             }
 135:             if (length < sourceArray.Length)
 136:             {
 137:                 byte[] destinationArray = new byte[length];
 138:                 Array.Copy(sourceArray, destinationArray, length);
 139:                 sourceArray = destinationArray;
 140:             }
 141:             return sourceArray;
 142:         }
 143:  
 144:         private static string UrlDecodeStringFromBytesInternal(byte[] buf, int offset, int count, Encoding e)
 145:         {
 146:             UrlDecoder decoder = new UrlDecoder(count, e);
 147:             for (int i = 0; i < count; i++)
 148:             {
 149:                 int index = offset + i;
 150:                 byte b = buf[index];
 151:                 if (b == 0x2b)
 152:                 {
 153:                     b = 0x20;
 154:                 }
 155:                 else if ((b == 0x25) && (i < (count - 2)))
 156:                 {
 157:                     if ((buf[index + 1] == 0x75) && (i < (count - 5)))
 158:                     {
 159:                         int num4 = HexToInt((char) buf[index + 2]);
 160:                         int num5 = HexToInt((char) buf[index + 3]);
 161:                         int num6 = HexToInt((char) buf[index + 4]);
 162:                         int num7 = HexToInt((char) buf[index + 5]);
 163:                         if (((num4 < 0) || (num5 < 0)) || ((num6 < 0) || (num7 < 0)))
 164:                         {
 165:                             goto Label_00DA;
 166:                         }
 167:                         char ch = (char) ((((num4 << 12) | (num5 << 8)) | (num6 << 4)) | num7);
 168:                         i += 5;
 169:                         decoder.AddChar(ch);
 170:                         continue;
 171:                     }
 172:                     int num8 = HexToInt((char) buf[index + 1]);
 173:                     int num9 = HexToInt((char) buf[index + 2]);
 174:                     if ((num8 >= 0) && (num9 >= 0))
 175:                     {
 176:                         b = (byte) ((num8 << 4) | num9);
 177:                         i += 2;
 178:                     }
 179:                 }
 180:                 Label_00DA:
 181:                 decoder.AddByte(b);
 182:             }
 183:             return decoder.GetString();
 184:         }
 185:  
 186:         private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
 187:         {
 188:             int length = s.Length;
 189:             UrlDecoder decoder = new UrlDecoder(length, e);
 190:             for (int i = 0; i < length; i++)
 191:             {
 192:                 char ch = s[i];
 193:                 if (ch == '+')
 194:                 {
 195:                     ch = ' ';
 196:                 }
 197:                 else if ((ch == '%') && (i < (length - 2)))
 198:                 {
 199:                     if ((s[i + 1] == 'u') && (i < (length - 5)))
 200:                     {
 201:                         int num3 = HexToInt(s[i + 2]);
 202:                         int num4 = HexToInt(s[i + 3]);
 203:                         int num5 = HexToInt(s[i + 4]);
 204:                         int num6 = HexToInt(s[i + 5]);
 205:                         if (((num3 < 0) || (num4 < 0)) || ((num5 < 0) || (num6 < 0)))
 206:                         {
 207:                             goto Label_0106;
 208:                         }
 209:                         ch = (char) ((((num3 << 12) | (num4 << 8)) | (num5 << 4)) | num6);
 210:                         i += 5;
 211:                         decoder.AddChar(ch);
 212:                         continue;
 213:                     }
 214:                     int num7 = HexToInt(s[i + 1]);
 215:                     int num8 = HexToInt(s[i + 2]);
 216:                     if ((num7 >= 0) && (num8 >= 0))
 217:                     {
 218:                         byte b = (byte) ((num7 << 4) | num8);
 219:                         i += 2;
 220:                         decoder.AddByte(b);
 221:                         continue;
 222:                     }
 223:                 }
 224:                 Label_0106:
 225:                 if ((ch & 0xff80) == 0)
 226:                 {
 227:                     decoder.AddByte((byte) ch);
 228:                 }
 229:                 else
 230:                 {
 231:                     decoder.AddChar(ch);
 232:                 }
 233:             }
 234:             return decoder.GetString();
 235:         }
 236:  
 237:         public static byte[] UrlDecodeToBytes(byte[] bytes)
 238:         {
 239:             if (bytes == null)
 240:             {
 241:                 return null;
 242:             }
 243:             return UrlDecodeToBytes(bytes, 0, (bytes != null) ? bytes.Length : 0);
 244:         }
 245:  
 246:         public static byte[] UrlDecodeToBytes(string str)
 247:         {
 248:             if (str == null)
 249:             {
 250:                 return null;
 251:             }
 252:             return UrlDecodeToBytes(str, Encoding.UTF8);
 253:         }
 254:  
 255:         public static byte[] UrlDecodeToBytes(string str, Encoding e)
 256:         {
 257:             if (str == null)
 258:             {
 259:                 return null;
 260:             }
 261:             return UrlDecodeToBytes(e.GetBytes(str));
 262:         }
 263:  
 264:         public static byte[] UrlDecodeToBytes(byte[] bytes, int offset, int count)
 265:         {
 266:             if ((bytes == null) && (count == 0))
 267:             {
 268:                 return null;
 269:             }
 270:             if (bytes == null)
 271:             {
 272:                 throw new ArgumentNullException("bytes");
 273:             }
 274:             if ((offset < 0) || (offset > bytes.Length))
 275:             {
 276:                 throw new ArgumentOutOfRangeException("offset");
 277:             }
 278:             if ((count < 0) || ((offset + count) > bytes.Length))
 279:             {
 280:                 throw new ArgumentOutOfRangeException("count");
 281:             }
 282:             return UrlDecodeBytesFromBytesInternal(bytes, offset, count);
 283:         }
 284:  
 285:         public static string UrlEncode(byte[] bytes)
 286:         {
 287:             if (bytes == null)
 288:             {
 289:                 return null;
 290:             }
 291:             return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes));
 292:         }
 293:  
 294:         public static string UrlEncode(string str)
 295:         {
 296:             if (str == null)
 297:             {
 298:                 return null;
 299:             }
 300:             return UrlEncode(str, Encoding.UTF8);
 301:         }
 302:  
 303:         public static string UrlEncode(string str, Encoding e)
 304:         {
 305:             if (str == null)
 306:             {
 307:                 return null;
 308:             }
 309:             return Encoding.ASCII.GetString(UrlEncodeToBytes(str, e));
 310:         }
 311:  
 312:         public static string UrlEncode(byte[] bytes, int offset, int count)
 313:         {
 314:             if (bytes == null)
 315:             {
 316:                 return null;
 317:             }
 318:             return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, offset, count));
 319:         }
 320:  
 321:         private static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count,
 322:                                                             bool alwaysCreateReturnValue)
 323:         {
 324:             int num = 0;
 325:             int num2 = 0;
 326:             for (int i = 0; i < count; i++)
 327:             {
 328:                 char ch = (char) bytes[offset + i];
 329:                 if (ch == ' ')
 330:                 {
 331:                     num++;
 332:                 }
 333:                 else if (!IsSafe(ch))
 334:                 {
 335:                     num2++;
 336:                 }
 337:             }
 338:             if ((!alwaysCreateReturnValue && (num == 0)) && (num2 == 0))
 339:             {
 340:                 return bytes;
 341:             }
 342:             byte[] buffer = new byte[count + (num2*2)];
 343:             int num4 = 0;
 344:             for (int j = 0; j < count; j++)
 345:             {
 346:                 byte num6 = bytes[offset + j];
 347:                 char ch2 = (char) num6;
 348:                 if (IsSafe(ch2))
 349:                 {
 350:                     buffer[num4++] = num6;
 351:                 }
 352:                 else if (ch2 == ' ')
 353:                 {
 354:                     buffer[num4++] = 0x2b;
 355:                 }
 356:                 else
 357:                 {
 358:                     buffer[num4++] = 0x25;
 359:                     buffer[num4++] = (byte) IntToHex((num6 >> 4) & 15);
 360:                     buffer[num4++] = (byte) IntToHex(num6 & 15);
 361:                 }
 362:             }
 363:             return buffer;
 364:         }
 365:  
 366:         private static byte[] UrlEncodeBytesToBytesInternalNonAscii(byte[] bytes, int offset, int count,
 367:                                                                     bool alwaysCreateReturnValue)
 368:         {
 369:             int num = 0;
 370:             for (int i = 0; i < count; i++)
 371:             {
 372:                 if (IsNonAsciiByte(bytes[offset + i]))
 373:                 {
 374:                     num++;
 375:                 }
 376:             }
 377:             if (!alwaysCreateReturnValue && (num == 0))
 378:             {
 379:                 return bytes;
 380:             }
 381:             byte[] buffer = new byte[count + (num*2)];
 382:             int num3 = 0;
 383:             for (int j = 0; j < count; j++)
 384:             {
 385:                 byte b = bytes[offset + j];
 386:                 if (IsNonAsciiByte(b))
 387:                 {
 388:                     buffer[num3++] = 0x25;
 389:                     buffer[num3++] = (byte) IntToHex((b >> 4) & 15);
 390:                     buffer[num3++] = (byte) IntToHex(b & 15);
 391:                 }
 392:                 else
 393:                 {
 394:                     buffer[num3++] = b;
 395:                 }
 396:             }
 397:             return buffer;
 398:         }
 399:  
 400:         internal static string UrlEncodeNonAscii(string str, Encoding e)
 401:         {
 402:             if (string.IsNullOrEmpty(str))
 403:             {
 404:                 return str;
 405:             }
 406:             if (e == null)
 407:             {
 408:                 e = Encoding.UTF8;
 409:             }
 410:             byte[] bytes = e.GetBytes(str);
 411:             bytes = UrlEncodeBytesToBytesInternalNonAscii(bytes, 0, bytes.Length, false);
 412:             return Encoding.ASCII.GetString(bytes);
 413:         }
 414:  
 415:         internal static string UrlEncodeSpaces(string str)
 416:         {
 417:             if ((str != null) && (str.IndexOf(' ') >= 0))
 418:             {
 419:                 str = str.Replace(" ", "%20");
 420:             }
 421:             return str;
 422:         }
 423:  
 424:         public static byte[] UrlEncodeToBytes(string str)
 425:         {
 426:             if (str == null)
 427:             {
 428:                 return null;
 429:             }
 430:             return UrlEncodeToBytes(str, Encoding.UTF8);
 431:         }
 432:  
 433:         public static byte[] UrlEncodeToBytes(byte[] bytes)
 434:         {
 435:             if (bytes == null)
 436:             {
 437:                 return null;
 438:             }
 439:             return UrlEncodeToBytes(bytes, 0, bytes.Length);
 440:         }
 441:  
 442:         public static byte[] UrlEncodeToBytes(string str, Encoding e)
 443:         {
 444:             if (str == null)
 445:             {
 446:                 return null;
 447:             }
 448:             byte[] bytes = e.GetBytes(str);
 449:             return UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false);
 450:         }
 451:  
 452:         public static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count)
 453:         {
 454:             if ((bytes == null) && (count == 0))
 455:             {
 456:                 return null;
 457:             }
 458:             if (bytes == null)
 459:             {
 460:                 throw new ArgumentNullException("bytes");
 461:             }
 462:             if ((offset < 0) || (offset > bytes.Length))
 463:             {
 464:                 throw new ArgumentOutOfRangeException("offset");
 465:             }
 466:             if ((count < 0) || ((offset + count) > bytes.Length))
 467:             {
 468:                 throw new ArgumentOutOfRangeException("count");
 469:             }
 470:             return UrlEncodeBytesToBytesInternal(bytes, offset, count, true);
 471:         }
 472:  
 473:         public static string UrlEncodeUnicode(string str)
 474:         {
 475:             if (str == null)
 476:             {
 477:                 return null;
 478:             }
 479:             return UrlEncodeUnicodeStringToStringInternal(str, false);
 480:         }
 481:  
 482:         private static string UrlEncodeUnicodeStringToStringInternal(string s, bool ignoreAscii)
 483:         {
 484:             int length = s.Length;
 485:             StringBuilder builder = new StringBuilder(length);
 486:             for (int i = 0; i < length; i++)
 487:             {
 488:                 char ch = s[i];
 489:                 if ((ch & 0xff80) == 0)
 490:                 {
 491:                     if (ignoreAscii || IsSafe(ch))
 492:                     {
 493:                         builder.Append(ch);
 494:                     }
 495:                     else if (ch == ' ')
 496:                     {
 497:                         builder.Append('+');
 498:                     }
 499:                     else
 500:                     {
 501:                         builder.Append('%');
 502:                         builder.Append(IntToHex((ch >> 4) & '\x000f'));
 503:                         builder.Append(IntToHex(ch & '\x000f'));
 504:                     }
 505:                 }
 506:                 else
 507:                 {
 508:                     builder.Append("%u");
 509:                     builder.Append(IntToHex((ch >> 12) & '\x000f'));
 510:                     builder.Append(IntToHex((ch >> 8) & '\x000f'));
 511:                     builder.Append(IntToHex((ch >> 4) & '\x000f'));
 512:                     builder.Append(IntToHex(ch & '\x000f'));
 513:                 }
 514:             }
 515:             return builder.ToString();
 516:         }
 517:  
 518:         public static byte[] UrlEncodeUnicodeToBytes(string str)
 519:         {
 520:             if (str == null)
 521:             {
 522:                 return null;
 523:             }
 524:             return Encoding.ASCII.GetBytes(UrlEncodeUnicode(str));
 525:         }
 526:  
 527:         public static string UrlPathEncode(string str)
 528:         {
 529:             if (str == null)
 530:             {
 531:                 return null;
 532:             }
 533:             int index = str.IndexOf('?');
 534:             if (index >= 0)
 535:             {
 536:                 return (UrlPathEncode(str.Substring(0, index)) + str.Substring(index));
 537:             }
 538:             return UrlEncodeSpaces(UrlEncodeNonAscii(str, Encoding.UTF8));
 539:         }
 540:  
 541:         // Nested Types
 542:         private class UrlDecoder
 543:         {
 544:             // Fields
 545:             private int _bufferSize;
 546:             private byte[] _byteBuffer;
 547:             private char[] _charBuffer;
 548:             private Encoding _encoding;
 549:             private int _numBytes;
 550:             private int _numChars;
 551:  
 552:             // Methods
 553:             internal UrlDecoder(int bufferSize, Encoding encoding)
 554:             {
 555:                 _bufferSize = bufferSize;
 556:                 _encoding = encoding;
 557:                 _charBuffer = new char[bufferSize];
 558:             }
 559:  
 560:             internal void AddByte(byte b)
 561:             {
 562:                 if (_byteBuffer == null)
 563:                 {
 564:                     _byteBuffer = new byte[_bufferSize];
 565:                 }
 566:                 _byteBuffer[_numBytes++] = b;
 567:             }
 568:  
 569:             internal void AddChar(char ch)
 570:             {
 571:                 if (_numBytes > 0)
 572:                 {
 573:                     FlushBytes();
 574:                 }
 575:                 _charBuffer[_numChars++] = ch;
 576:             }
 577:  
 578:             private void FlushBytes()
 579:             {
 580:                 if (_numBytes > 0)
 581:                 {
 582:                     _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
 583:                     _numBytes = 0;
 584:                 }
 585:             }
 586:  
 587:             internal string GetString()
 588:             {
 589:                 if (_numBytes > 0)
 590:                 {
 591:                     FlushBytes();
 592:                 }
 593:                 if (_numChars > 0)
 594:                 {
 595:                     return new string(_charBuffer, 0, _numChars);
 596:                 }
 597:                 return string.Empty;
 598:             }
 599:         }
 600:     }
 601: }

Note: The source for HttpUtility is also available through the .NET Framework source release, but I actually found it easier to get a clean copy by decompiling.

The code above was formatted using Leo Vildosola's Code Snippet plugin for Windows Live Writer, which I just started using with this post.

3 comments:

Dan said...

How did you get around the .NET Client Profile install restrictions when any version of .NET 2.0 is already installed?

Check out this handy reference.

Ashley Tate said...

(I'm assuming you meant to comment on this post again.)

You aren't actually prevented from installing the client profile, it just quietly installs the full 3.5 SP1 framework instead if there's an existing version. From the document you referenced:


NOTE: The .NET Framework Client Profile is targeted for Windows XP computers with no .NET Framework components installed. If the .NET Framework Client Profile installation process detects any other operating system version or any version of .NET Framework installed, the Client Profile installer will install .NET Framework 3.5 Service Pack 1.


But I had not realized this was happening before looking at that reference. It explains why there was no performance advantage when attempting an upgrade with the client profile.

Ashley Tate said...

Actually, I should clarify. You may have been looking at the section for administrators where it says:


NOTE: The .NET Framework Client Profile is not intended to function with any other versions of the .NET Framework. If any version, other than the latest version of the .NET Framework, is to be installed, the .NET Framework Client Profile will halt that installation and prompt the user to install the full .NET Framework 3.5 Service Pack 1 or later.


I'm not sure if this is only referring to unattended installations or if they changed the behavior, but there were no prompts during my test upgrades

 
Header photo courtesy of: http://www.flickr.com/photos/tmartin/ / CC BY-NC 2.0