Fix to checked-build reference auditing
[mono.git] / mcs / class / System.ServiceModel.Web / System.Runtime.Serialization.Json / JsonReader.cs
index 8954bf291c4dc37f94fe9842ee087702c9e68ad7..a5cff6dfbf52072817250fde9c6f744510f4fbb0 100644 (file)
@@ -1,10 +1,10 @@
 //
-// JsonWriter.cs
+// JsonReader.cs
 //
 // Author:
 //     Atsushi Enomoto  <atsushi@ximian.com>
 //
-// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2007-2011 Novell, Inc (http://www.novell.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -34,6 +34,262 @@ using System.Xml;
 
 namespace System.Runtime.Serialization.Json
 {
+       // It is a subset of XmlInputStream from System.XML.
+       class EncodingDetecingInputStream : Stream
+       {
+               internal static readonly Encoding StrictUTF8, Strict1234UTF32, StrictBigEndianUTF16, StrictUTF16;
+
+               static EncodingDetecingInputStream ()
+               {
+                       StrictUTF8 = new UTF8Encoding (false, true);
+                       Strict1234UTF32 = new UTF32Encoding (true, false, true);
+                       StrictBigEndianUTF16 = new UnicodeEncoding (true, false, true);
+                       StrictUTF16 = new UnicodeEncoding (false, false, true);
+               }
+
+               Encoding enc;
+               Stream stream;
+               byte[] buffer;
+               int bufLength;
+               int bufPos;
+
+               static XmlException encodingException = new XmlException ("invalid encoding specification.");
+
+               public EncodingDetecingInputStream (Stream stream)
+               {
+                       if (stream == null)
+                               throw new ArgumentNullException ("stream");
+                       Initialize (stream);
+               }
+
+               private void Initialize (Stream stream)
+               {
+                       buffer = new byte [6];
+                       this.stream = stream;
+                       enc = StrictUTF8; // Default to UTF8 if we can't guess it
+                       bufLength = stream.Read (buffer, 0, buffer.Length);
+                       if (bufLength == -1 || bufLength == 0) {
+                               return;
+                       }
+
+                       int c = ReadByteSpecial ();
+                       switch (c) {
+                       case 0xFF:
+                               c = ReadByteSpecial ();
+                               if (c == 0xFE) {
+                                       // BOM-ed little endian utf-16
+                                       enc = Encoding.Unicode;
+                               } else {
+                                       // It doesn't start from "<?xml" then its encoding is utf-8
+                                       bufPos = 0;
+                               }
+                               break;
+                       case 0xFE:
+                               c = ReadByteSpecial ();
+                               if (c == 0xFF) {
+                                       // BOM-ed big endian utf-16
+                                       enc = Encoding.BigEndianUnicode;
+                                       return;
+                               } else {
+                                       // It doesn't start from "<?xml" then its encoding is utf-8
+                                       bufPos = 0;
+                               }
+                               break;
+                       case 0xEF:
+                               c = ReadByteSpecial ();
+                               if (c == 0xBB) {
+                                       c = ReadByteSpecial ();
+                                       if (c != 0xBF) {
+                                               bufPos = 0;
+                                       }
+                               } else {
+                                       buffer [--bufPos] = 0xEF;
+                               }
+                               break;
+                       case 0:
+                               // It could still be 1234/2143/3412 variants of UTF32, but only 1234 version is available on .NET.
+                               c = ReadByteSpecial ();
+                               if (c == 0)
+                                       enc = Strict1234UTF32;
+                               else
+                                       enc = StrictBigEndianUTF16;
+                               break;
+                       default:
+                               c = ReadByteSpecial ();
+                               if (c == 0)
+                                       enc = StrictUTF16;
+                               bufPos = 0;
+                               break;
+                       }
+               }
+
+               // Just like readbyte, but grows the buffer too.
+               int ReadByteSpecial ()
+               {
+                       if (bufLength > bufPos)
+                               return buffer [bufPos++];
+
+                       byte [] newbuf = new byte [buffer.Length * 2];
+                       Buffer.BlockCopy (buffer, 0, newbuf, 0, bufLength);
+                       int nbytes = stream.Read (newbuf, bufLength, buffer.Length);
+                       if (nbytes == -1 || nbytes == 0)
+                               return -1;
+                               
+                       bufLength += nbytes;
+                       buffer = newbuf;
+                       return buffer [bufPos++];
+               }
+
+               public Encoding ActualEncoding {
+                       get { return enc; }
+               }
+
+               #region Public Overrides
+               public override bool CanRead {
+                       get {
+                               if (bufLength > bufPos)
+                                       return true;
+                               else
+                                       return stream.CanRead; 
+                       }
+               }
+
+               // FIXME: It should support base stream's CanSeek.
+               public override bool CanSeek {
+                       get { return false; } // stream.CanSeek; }
+               }
+
+               public override bool CanWrite {
+                       get { return false; }
+               }
+
+               public override long Length {
+                       get {
+                               return stream.Length;
+                       }
+               }
+
+               public override long Position {
+                       get {
+                               return stream.Position - bufLength + bufPos;
+                       }
+                       set {
+                               if(value < bufLength)
+                                       bufPos = (int)value;
+                               else
+                                       stream.Position = value - bufLength;
+                       }
+               }
+
+               public override void Close ()
+               {
+                       stream.Close ();
+               }
+
+               public override void Flush ()
+               {
+                       stream.Flush ();
+               }
+
+               public override int Read (byte[] buffer, int offset, int count)
+               {
+                       int ret;
+                       if (count <= bufLength - bufPos)        {       // all from buffer
+                               Buffer.BlockCopy (this.buffer, bufPos, buffer, offset, count);
+                               bufPos += count;
+                               ret = count;
+                       } else {
+                               int bufRest = bufLength - bufPos;
+                               if (bufLength > bufPos) {
+                                       Buffer.BlockCopy (this.buffer, bufPos, buffer, offset, bufRest);
+                                       bufPos += bufRest;
+                               }
+                               ret = bufRest +
+                                       stream.Read (buffer, offset + bufRest, count - bufRest);
+                       }
+                       return ret;
+               }
+
+               public override int ReadByte ()
+               {
+                       if (bufLength > bufPos) {
+                               return buffer [bufPos++];
+                       }
+                       return stream.ReadByte ();
+               }
+
+               public override long Seek (long offset, System.IO.SeekOrigin origin)
+               {
+                       int bufRest = bufLength - bufPos;
+                       if (origin == SeekOrigin.Current)
+                               if (offset < bufRest)
+                                       return buffer [bufPos + offset];
+                               else
+                                       return stream.Seek (offset - bufRest, origin);
+                       else
+                               return stream.Seek (offset, origin);
+               }
+
+               public override void SetLength (long value)
+               {
+                       stream.SetLength (value);
+               }
+
+               public override void Write (byte[] buffer, int offset, int count)
+               {
+                       throw new NotSupportedException ();
+               }
+               #endregion
+       }
+
+       class PushbackReader : StreamReader
+       {
+               Stack<int> pushback;
+
+               public PushbackReader (Stream stream, Encoding encoding) : base (stream, encoding)
+               {
+                       pushback = new Stack<int>();
+               }
+
+               public PushbackReader (Stream stream) : this (new EncodingDetecingInputStream (stream))
+               {
+               }
+
+               public PushbackReader (EncodingDetecingInputStream stream) : this (stream, stream.ActualEncoding)
+               {
+               }
+
+               public override void Close ()
+               {
+                       pushback.Clear ();
+               }
+
+               public override int Peek ()
+               {
+                       if (pushback.Count > 0) {
+                               return pushback.Peek ();
+                       }
+                       else {
+                               return base.Peek ();
+                       }
+               }
+
+               public override int Read ()
+               {
+                       if (pushback.Count > 0) {
+                               return pushback.Pop ();
+                       }
+                       else {
+                               return base.Read ();
+                       }
+               }
+
+               public void Pushback (int ch)
+               {
+                       pushback.Push (ch);
+               }
+       }
+
        // FIXME: quotas check
        class JsonReader : XmlDictionaryReader, IXmlJsonReaderInitializer, IXmlLineInfo
        {
@@ -59,7 +315,7 @@ namespace System.Runtime.Serialization.Json
                        RuntimeTypeValue
                }
 
-               TextReader reader;
+               PushbackReader reader;
                XmlDictionaryReaderQuotas quotas;
                OnXmlDictionaryReaderClose on_close;
                XmlNameTable name_table = new NameTable ();
@@ -76,8 +332,6 @@ namespace System.Runtime.Serialization.Json
 
                int line = 1, column = 0;
 
-               int saved_char = -1;
-
                // Constructors
 
                public JsonReader (byte [] buffer, int offset, int count, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
@@ -116,7 +370,10 @@ namespace System.Runtime.Serialization.Json
 
                public void SetInput (Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
                {
-                       reader = new StreamReader (stream, encoding ?? Encoding.UTF8);
+                       if (encoding != null)
+                               reader = new PushbackReader (stream, encoding);
+                       else
+                               reader = new PushbackReader (stream);
                        if (quotas == null)
                                throw new ArgumentNullException ("quotas");
                        this.quotas = quotas;
@@ -376,6 +633,8 @@ namespace System.Runtime.Serialization.Json
                                break;
                        }
 
+                       MoveToElement ();
+
                        if (content_stored) {
                                if (current_node == XmlNodeType.Element) {
                                        if (elements.Peek ().Type == "null") {
@@ -414,6 +673,20 @@ namespace System.Runtime.Serialization.Json
                        return true;
                }
 
+               bool TryReadString (string str)
+               {
+                       for (int i = 0; i < str.Length; i ++) {
+                               int ch = ReadChar ();
+                               if (ch != str[i]) {
+                                       for (int j = i; j >= 0; j--)
+                                               PushbackChar (j);
+                                       return false;
+                               }
+                       }
+
+                       return true;
+               }
+
                bool ReadContent (bool objectValue)
                {
                        int ch = ReadChar ();
@@ -490,37 +763,47 @@ namespace System.Runtime.Serialization.Json
                                ReadNumber (ch);
                                return true;
                        case 'n':
-                               if (ReadChar () == 'u' &&
-                                   ReadChar () == 'l' &&
-                                   ReadChar () == 'l') {
-                                       ReadAsSimpleContent ("null", "null");
+                               if (TryReadString("ull")) {
+                                       ReadAsSimpleContent ("null", "null");
                                        return true;
                                }
-                               goto default;
+                               else {
+                                       // the pushback for 'n' is taken care of by the
+                                       // default case if we're in lame silverlight literal
+                                       // mode
+                                       goto default;
+                               }
                        case 't':
-                               if (ReadChar () == 'r' &&
-                                   ReadChar () == 'u' &&
-                                   ReadChar () == 'e') {
-                                       ReadAsSimpleContent ("boolean", "true");
+                               if (TryReadString ("rue")) {
+                                       ReadAsSimpleContent ("boolean", "true");
                                        return true;
                                }
-                               goto default;
+                               else {
+                                       // the pushback for 't' is taken care of by the
+                                       // default case if we're in lame silverlight literal
+                                       // mode
+                                       goto default;
+                               }
                        case 'f':
-                               if (ReadChar () == 'a' &&
-                                   ReadChar () == 'l' &&
-                                   ReadChar () == 's' &&
-                                   ReadChar () == 'e') {
-                                       ReadAsSimpleContent ("boolean", "false");
+                               if (TryReadString ("alse")) {
+                                       ReadAsSimpleContent ("boolean", "false");
                                        return true;
                                }
-                               goto default;
+                               else {
+                                       // the pushback for 'f' is taken care of by the
+                                       // default case if we're in lame silverlight literal
+                                       // mode
+                                       goto default;
+                               }
                        default:
                                if ('0' <= ch && ch <= '9') {
                                        ReadNumber (ch);
                                        return true;
                                }
-                               if (LameSilverlightLiteralParser)
+                               if (LameSilverlightLiteralParser) {
+                                       PushbackChar (ch);
                                        goto case '"';
+                               }
                                throw XmlError (String.Format ("Unexpected token: '{0}' ({1:X04})", (char) ch, (int) ch));
                        }
                }
@@ -617,9 +900,14 @@ namespace System.Runtime.Serialization.Json
                                                throw XmlError ("Invalid JSON number token. '.' must not occur after 'E' or 'e'");
                                        floating = true;
                                        break;
+                               case '+':
+                               case '-':
+                                       if (prev == 'E' || prev == 'e')
+                                               break;
+                                       goto default;
                                default:
                                        if (!IsNumber (ch)) {
-                                               saved_char = ch;
+                                               PushbackChar (ch);
                                                cont = false;
                                        }
                                        break;
@@ -711,15 +999,12 @@ namespace System.Runtime.Serialization.Json
 
                int PeekChar ()
                {
-                       if (saved_char < 0)
-                               saved_char = reader.Read ();
-                       return saved_char;
+                       return reader.Peek ();
                }
 
                int ReadChar ()
                {
-                       int v = saved_char >= 0 ? saved_char : reader.Read ();
-                       saved_char = -1;
+                       int v = reader.Read ();
                        if (v == '\n') {
                                line++;
                                column = 0;
@@ -729,6 +1014,12 @@ namespace System.Runtime.Serialization.Json
                        return v;
                }
 
+               void PushbackChar (int ch)
+               {
+                       // FIXME handle lines (and columns?  ugh, how?)
+                       reader.Pushback (ch);
+               }
+
                void SkipWhitespaces ()
                {
                        do {
@@ -758,5 +1049,51 @@ namespace System.Runtime.Serialization.Json
                {
                        return new XmlException (String.Format ("{0} ({1},{2})", s, line, column));
                }
+
+               // This reads the current element and all its content as a string,
+               // with no processing done except for advancing the reader.
+               public override string ReadInnerXml ()
+               {
+
+                       if (NodeType != XmlNodeType.Element)
+                               return base.ReadInnerXml ();
+
+                       StringBuilder sb = new StringBuilder ();
+                       bool isobject = elements.Peek ().Type == "object";
+                       char end = isobject ? '}' : ']';
+                       char start = isobject ? '{' : '[';
+                       int count = 1;
+
+                       sb.Append (start);
+
+                       // add the first child manually, it's already been read
+                       // but hasn't been processed yet
+                       if (isobject && !String.IsNullOrEmpty (next_object_content_name))
+                                       sb.Append ("\"" + next_object_content_name + "\"");
+
+                       // keep reading until we hit the end marker, no processing is
+                       // done on anything
+                       do {
+                               char c = (char)ReadChar ();
+                               sb.Append (c);
+                               if (c == start)
+                                       ++count;
+                               else if (c == end)
+                                       --count;
+                       } while (count > 0);
+
+                       // Replace the content we've read with an empty object so it gets
+                       // skipped on the following Read
+                       reader.Pushback (end);
+                       if (isobject) {
+                               reader.Pushback ('"');
+                               reader.Pushback ('"');
+                               reader.Pushback (':');
+                       }
+
+                       // Skip the element
+                       Read ();
+                       return sb.ToString ();
+               }
        }
 }