Last change for http support, adding a simple reader for query strings and/or
url-encoded form data to support restful apis.  Also exposed the mime to reader
and mime to writer via dictionaries in the MessageFormatOptions structure.
diff --git a/src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs b/src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs
new file mode 100644
index 0000000..508d76a
--- /dev/null
+++ b/src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs
@@ -0,0 +1,162 @@
+using System;

+using System.IO;

+using System.Text;

+

+namespace Google.ProtocolBuffers.Serialization.Http

+{

+    /// <summary>

+    /// Allows reading messages from a name/value dictionary

+    /// </summary>

+    public class FormUrlEncodedReader : AbstractTextReader

+    {

+        private readonly TextReader _input;

+        private string _fieldName, _fieldValue;

+        private bool _ready;

+

+        /// <summary>

+        /// Creates a dictionary reader from an enumeration of KeyValuePair data, like an IDictionary

+        /// </summary>

+        FormUrlEncodedReader(TextReader input)

+        {

+            _input = input;

+            int ch = input.Peek();

+            if (ch == '?')

+            {

+                input.Read();

+            }

+            _ready = ReadNext();

+        }

+

+        #region CreateInstance overloads

+        /// <summary>

+        /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.

+        /// </summary>

+        public static FormUrlEncodedReader CreateInstance(Stream stream)

+        {

+            return new FormUrlEncodedReader(new StreamReader(stream, Encoding.UTF8, false));

+        }

+

+        /// <summary>

+        /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.

+        /// </summary>

+        public static FormUrlEncodedReader CreateInstance(byte[] bytes)

+        {

+            return new FormUrlEncodedReader(new StreamReader(new MemoryStream(bytes, false), Encoding.UTF8, false));

+        }

+

+        /// <summary>

+        /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.

+        /// </summary>

+        public static FormUrlEncodedReader CreateInstance(string text)

+        {

+            return new FormUrlEncodedReader(new StringReader(text));

+        }

+

+        /// <summary>

+        /// Constructs a FormUrlEncodedReader to parse form data, or url query text into a message.

+        /// </summary>

+        public static FormUrlEncodedReader CreateInstance(TextReader input)

+        {

+            return new FormUrlEncodedReader(input);

+        }

+        #endregion

+

+        private bool ReadNext()

+        {

+            StringBuilder field = new StringBuilder(32);

+            StringBuilder value = new StringBuilder(64);

+            int ch;

+            while (-1 != (ch = _input.Read()) && ch != '=' && ch != '&')

+            {

+                field.Append((char)ch);

+            }

+

+            if (ch != -1 && ch != '&')

+            {

+                while (-1 != (ch = _input.Read()) && ch != '&')

+                {

+                    value.Append((char)ch);

+                }

+            }

+

+            _fieldName = field.ToString();

+            _fieldValue = Uri.UnescapeDataString(value.Replace('+', ' ').ToString());

+            

+            return !String.IsNullOrEmpty(_fieldName);

+        }

+

+        /// <summary>

+        /// No-op

+        /// </summary>

+        public override void ReadMessageStart()

+        { }

+

+        /// <summary>

+        /// No-op

+        /// </summary>

+        public override void ReadMessageEnd()

+        { }

+

+        /// <summary>

+        /// Merges the contents of stream into the provided message builder

+        /// </summary>

+        public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)

+        {

+            builder.WeakMergeFrom(this, registry);

+            return builder;

+        }

+

+        /// <summary>

+        /// Causes the reader to skip past this field

+        /// </summary>

+        protected override void Skip()

+        {

+            _ready = ReadNext();

+        }

+

+        /// <summary>

+        /// Peeks at the next field in the input stream and returns what information is available.

+        /// </summary>

+        /// <remarks>

+        /// This may be called multiple times without actually reading the field.  Only after the field

+        /// is either read, or skipped, should PeekNext return a different value.

+        /// </remarks>

+        protected override bool PeekNext(out string field)

+        {

+            field = _ready ? _fieldName : null;

+            return field != null;

+        }

+

+        /// <summary>

+        /// Returns true if it was able to read a String from the input

+        /// </summary>

+        protected override bool ReadAsText(ref string value, Type typeInfo)

+        {

+            if (_ready)

+            {

+                value = _fieldValue;

+                _ready = ReadNext();

+                return true;

+            }

+            return false;

+        }

+

+        /// <summary>

+        /// It's unlikely this will work for anything but text data as bytes UTF8 are transformed to text and back to bytes

+        /// </summary>

+        protected override ByteString DecodeBytes(string bytes)

+        { return ByteString.CopyFromUtf8(bytes); }

+

+        /// <summary>

+        /// Not Supported

+        /// </summary>

+        public override bool ReadGroup(IBuilderLite value, ExtensionRegistry registry)

+        { throw new NotSupportedException(); }

+

+        /// <summary>

+        /// Not Supported

+        /// </summary>

+        protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)

+        { throw new NotSupportedException(); }

+    }

+}
\ No newline at end of file
diff --git a/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs b/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs
index 2ee7eb4..8d389f0 100644
--- a/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs
+++ b/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs
@@ -19,27 +19,14 @@
         /// <returns>The ICodedInputStream that can be given to the IBuilder.MergeFrom(...) method</returns>

         public static ICodedInputStream CreateInputStream(MessageFormatOptions options, string contentType, Stream input)

         {

-            FormatType inputType = ContentTypeToFormat(contentType, options.DefaultContentType);

+            ICodedInputStream codedInput = ContentTypeToInputStream(contentType, options, input);

 

-            ICodedInputStream codedInput;

-            if (inputType == FormatType.ProtoBuffer)

+            if (codedInput is XmlFormatReader)

             {

-                codedInput = CodedInputStream.CreateInstance(input);

-            }

-            else if (inputType == FormatType.Json)

-            {

-                JsonFormatReader reader = JsonFormatReader.CreateInstance(input);

-                codedInput = reader;

-            }

-            else if (inputType == FormatType.Xml)

-            {

-                XmlFormatReader reader = XmlFormatReader.CreateInstance(input);

+                XmlFormatReader reader = (XmlFormatReader)codedInput;

                 reader.RootElementName = options.XmlReaderRootElementName;

                 reader.Options = options.XmlReaderOptions;

-                codedInput = reader;

             }

-            else

-                throw new NotSupportedException();

 

             return codedInput;

         }

@@ -69,30 +56,20 @@
         /// <remarks> If you do not dispose of ICodedOutputStream some formats may yield incomplete output </remarks>

         public static ICodedOutputStream CreateOutputStream(MessageFormatOptions options, string contentType, Stream output)

         {

-            FormatType outputType = ContentTypeToFormat(contentType, options.DefaultContentType);

+            ICodedOutputStream codedOutput = ContentTypeToOutputStream(contentType, options, output);

 

-            ICodedOutputStream codedOutput;

-            if (outputType == FormatType.ProtoBuffer)

+            if (codedOutput is JsonFormatWriter)

             {

-                codedOutput = CodedOutputStream.CreateInstance(output);

-            }

-            else if (outputType == FormatType.Json)

-            {

-                JsonFormatWriter writer = JsonFormatWriter.CreateInstance(output);

+                JsonFormatWriter writer = (JsonFormatWriter)codedOutput;

                 if (options.FormattedOutput)

                 {

                     writer.Formatted();

                 }

-                codedOutput = writer;

             }

-            else if (outputType == FormatType.Xml)

+            else if (codedOutput is XmlFormatWriter)

             {

-                XmlFormatWriter writer;

-                if (!options.FormattedOutput)

-                {

-                    writer = XmlFormatWriter.CreateInstance(output);

-                }

-                else

+                XmlFormatWriter writer = (XmlFormatWriter)codedOutput;

+                if (options.FormattedOutput)

                 {

                     XmlWriterSettings settings = new XmlWriterSettings()

                                                      {

@@ -104,14 +81,12 @@
                                                          IndentChars = "    ",

                                                          NewLineChars = Environment.NewLine,

                                                      };

-                    writer = XmlFormatWriter.CreateInstance(XmlWriter.Create(output, settings));

+                    // Don't know how else to change xml writer options?

+                    codedOutput = writer = XmlFormatWriter.CreateInstance(XmlWriter.Create(output, settings));

                 }

                 writer.RootElementName = options.XmlWriterRootElementName;

                 writer.Options = options.XmlWriterOptions;

-                codedOutput = writer;

             }

-            else

-                throw new NotSupportedException();

 

             return codedOutput;

         }

@@ -137,46 +112,39 @@
             codedOutput.WriteMessageEnd();

         }

 

-        enum FormatType { ProtoBuffer, Json, Xml };

-

-        private static FormatType ContentTypeToFormat(string contentType, string defaultType)

+        private static ICodedInputStream ContentTypeToInputStream(string contentType, MessageFormatOptions options, Stream input)

         {

-            switch ((contentType ?? String.Empty).Split(';')[0].Trim().ToLower())

+            contentType = (contentType ?? String.Empty).Split(';')[0].Trim();

+

+            Converter<Stream, ICodedInputStream> factory;

+            if(!options.MimeInputTypesReadOnly.TryGetValue(contentType, out factory) || factory == null)

             {

-                case "application/json":

-                case "application/x-json":

-                case "application/x-javascript":

-                case "text/javascript":

-                case "text/x-javascript":

-                case "text/x-json":

-                case "text/json":

-                    {

-                        return FormatType.Json;

-                    }

-

-                case "text/xml":

-                case "application/xml":

-                    {

-                        return FormatType.Xml;

-                    }

-

-                case "application/binary":

-                case "application/x-protobuf":

-                case "application/vnd.google.protobuf":

-                    {

-                        return FormatType.ProtoBuffer;

-                    }

-

-                case "":

-                case null:

-                    if (!String.IsNullOrEmpty(defaultType))

-                    {

-                        return ContentTypeToFormat(defaultType, null);

-                    }

-                    break;

+                if(String.IsNullOrEmpty(options.DefaultContentType) ||

+                    !options.MimeInputTypesReadOnly.TryGetValue(options.DefaultContentType, out factory) || factory == null)

+                {

+                    throw new ArgumentOutOfRangeException("contentType");

+                }

             }

 

-            throw new ArgumentOutOfRangeException("contentType");

+            return factory(input);

         }

+

+        private static ICodedOutputStream ContentTypeToOutputStream(string contentType, MessageFormatOptions options, Stream output)

+        {

+            contentType = (contentType ?? String.Empty).Split(';')[0].Trim();

+

+            Converter<Stream, ICodedOutputStream> factory;

+            if (!options.MimeOutputTypesReadOnly.TryGetValue(contentType, out factory) || factory == null)

+            {

+                if (String.IsNullOrEmpty(options.DefaultContentType) ||

+                    !options.MimeOutputTypesReadOnly.TryGetValue(options.DefaultContentType, out factory) || factory == null)

+                {

+                    throw new ArgumentOutOfRangeException("contentType");

+                }

+            }

+

+            return factory(output);

+        }

+

     }

 }
\ No newline at end of file
diff --git a/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs b/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs
index 02f2ea4..4ee1889 100644
--- a/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs
+++ b/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs
@@ -1,4 +1,7 @@
 using System;

+using System.IO;

+using System.Collections.Generic;

+using Google.ProtocolBuffers.Collections;

 

 namespace Google.ProtocolBuffers.Serialization.Http

 {

@@ -22,10 +25,92 @@
         /// </remarks>

         public const string ContentTypeJson = "application/json";

 

+        /// <summary>The mime type for query strings and x-www-form-urlencoded content</summary>

+        /// <remarks>This mime type is input-only</remarks>

+        public const string ContentFormUrlEncoded = "application/x-www-form-urlencoded";

+

+        /// <summary>

+        /// Default mime-type handling for input

+        /// </summary>

+        private static readonly IDictionary<string, Converter<Stream, ICodedInputStream>> MimeInputDefaults =

+            new ReadOnlyDictionary<string, Converter<Stream, ICodedInputStream>>(

+            new Dictionary<string, Converter<Stream, ICodedInputStream>>(StringComparer.OrdinalIgnoreCase)

+                {

+                    {"application/json", JsonFormatReader.CreateInstance},

+                    {"application/x-json", JsonFormatReader.CreateInstance},

+                    {"application/x-javascript", JsonFormatReader.CreateInstance},

+                    {"text/javascript", JsonFormatReader.CreateInstance},

+                    {"text/x-javascript", JsonFormatReader.CreateInstance},

+                    {"text/x-json", JsonFormatReader.CreateInstance},

+                    {"text/json", JsonFormatReader.CreateInstance},

+                    {"text/xml", XmlFormatReader.CreateInstance},

+                    {"application/xml", XmlFormatReader.CreateInstance},

+                    {"application/binary", CodedInputStream.CreateInstance},

+                    {"application/x-protobuf", CodedInputStream.CreateInstance},

+                    {"application/vnd.google.protobuf", CodedInputStream.CreateInstance},

+                    {"application/x-www-form-urlencoded", FormUrlEncodedReader.CreateInstance},

+                }

+            );

+

+        /// <summary>

+        /// Default mime-type handling for output

+        /// </summary>

+        private static readonly IDictionary<string, Converter<Stream, ICodedOutputStream>> MimeOutputDefaults =

+            new ReadOnlyDictionary<string, Converter<Stream, ICodedOutputStream>>(

+            new Dictionary<string, Converter<Stream, ICodedOutputStream>>(StringComparer.OrdinalIgnoreCase)

+                {

+                    {"application/json", JsonFormatWriter.CreateInstance},

+                    {"application/x-json", JsonFormatWriter.CreateInstance},

+                    {"application/x-javascript", JsonFormatWriter.CreateInstance},

+                    {"text/javascript", JsonFormatWriter.CreateInstance},

+                    {"text/x-javascript", JsonFormatWriter.CreateInstance},

+                    {"text/x-json", JsonFormatWriter.CreateInstance},

+                    {"text/json", JsonFormatWriter.CreateInstance},

+                    {"text/xml", XmlFormatWriter.CreateInstance},

+                    {"application/xml", XmlFormatWriter.CreateInstance},

+                    {"application/binary", CodedOutputStream.CreateInstance},

+                    {"application/x-protobuf", CodedOutputStream.CreateInstance},

+                    {"application/vnd.google.protobuf", CodedOutputStream.CreateInstance},

+                }

+            );

+

+

+

+

         private string _defaultContentType;

         private string _xmlReaderRootElementName;

         private string _xmlWriterRootElementName;

         private ExtensionRegistry _extensionRegistry;

+        private Dictionary<string, Converter<Stream, ICodedInputStream>> _mimeInputTypes;

+        private Dictionary<string, Converter<Stream, ICodedOutputStream>> _mimeOutputTypes;

+

+        /// <summary> Provides access to modify the mime-type input stream construction </summary>

+        public IDictionary<string, Converter<Stream, ICodedInputStream>> MimeInputTypes

+        {

+            get

+            {

+                return _mimeInputTypes ??

+                    (_mimeInputTypes = new Dictionary<string, Converter<Stream, ICodedInputStream>>(

+                                           MimeInputDefaults, StringComparer.OrdinalIgnoreCase));

+            }

+        }

+

+        /// <summary> Provides access to modify the mime-type input stream construction </summary>

+        public IDictionary<string, Converter<Stream, ICodedOutputStream>> MimeOutputTypes

+        {

+            get

+            {

+                return _mimeOutputTypes ??

+                    (_mimeOutputTypes = new Dictionary<string, Converter<Stream, ICodedOutputStream>>(

+                                           MimeOutputDefaults, StringComparer.OrdinalIgnoreCase));

+            }

+        }

+

+        internal IDictionary<string, Converter<Stream, ICodedInputStream>> MimeInputTypesReadOnly

+        { get { return _mimeInputTypes ?? MimeInputDefaults; } }

+

+        internal IDictionary<string, Converter<Stream, ICodedOutputStream>> MimeOutputTypesReadOnly

+        { get { return _mimeOutputTypes ?? MimeOutputDefaults; } }

 

         /// <summary>

         /// The default content type to use if the input type is null or empty.  If this

diff --git a/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj b/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj
index fdbbe50..972fb14 100644
--- a/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj
+++ b/src/ProtocolBuffers.Serialization/ProtocolBuffers.Serialization.csproj
@@ -98,6 +98,7 @@
   </ItemGroup>

   <ItemGroup>

     <Compile Include="Extensions.cs" />

+    <Compile Include="Http\FormUrlEncodedReader.cs" />

     <Compile Include="Http\MessageFormatFactory.cs" />

     <Compile Include="Http\MessageFormatOptions.cs" />

     <Compile Include="Http\ServiceExtensions.cs" />