aboutsummaryrefslogtreecommitdiffhomepage
path: root/csharp/src/ProtocolBuffers.Serialization/Http
diff options
context:
space:
mode:
Diffstat (limited to 'csharp/src/ProtocolBuffers.Serialization/Http')
-rw-r--r--csharp/src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs162
-rw-r--r--csharp/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs112
-rw-r--r--csharp/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs176
3 files changed, 450 insertions, 0 deletions
diff --git a/csharp/src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs b/csharp/src/ProtocolBuffers.Serialization/Http/FormUrlEncodedReader.cs
new file mode 100644
index 00000000..508d76a9
--- /dev/null
+++ b/csharp/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/csharp/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs b/csharp/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs
new file mode 100644
index 00000000..270af64b
--- /dev/null
+++ b/csharp/src/ProtocolBuffers.Serialization/Http/MessageFormatFactory.cs
@@ -0,0 +1,112 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Text;
+
+namespace Google.ProtocolBuffers.Serialization.Http
+{
+ /// <summary>
+ /// Extensions and helpers to abstract the reading/writing of messages by a client-specified content type.
+ /// </summary>
+ public static class MessageFormatFactory
+ {
+ /// <summary>
+ /// Constructs an ICodedInputStream from the input stream based on the contentType provided
+ /// </summary>
+ /// <param name="options">Options specific to reading this message and/or content type</param>
+ /// <param name="contentType">The mime type of the input stream content</param>
+ /// <param name="input">The stream to read the message from</param>
+ /// <returns>The ICodedInputStream that can be given to the IBuilder.MergeFrom(...) method</returns>
+ public static ICodedInputStream CreateInputStream(MessageFormatOptions options, string contentType, Stream input)
+ {
+ ICodedInputStream codedInput = ContentTypeToInputStream(contentType, options, input);
+
+ if (codedInput is XmlFormatReader)
+ {
+ XmlFormatReader reader = (XmlFormatReader)codedInput;
+ reader.RootElementName = options.XmlReaderRootElementName;
+ reader.Options = options.XmlReaderOptions;
+ }
+
+ return codedInput;
+ }
+
+ /// <summary>
+ /// Writes the message instance to the stream using the content type provided
+ /// </summary>
+ /// <param name="options">Options specific to writing this message and/or content type</param>
+ /// <param name="contentType">The mime type of the content to be written</param>
+ /// <param name="output">The stream to write the message to</param>
+ /// <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)
+ {
+ ICodedOutputStream codedOutput = ContentTypeToOutputStream(contentType, options, output);
+
+ if (codedOutput is JsonFormatWriter)
+ {
+ JsonFormatWriter writer = (JsonFormatWriter)codedOutput;
+ if (options.FormattedOutput)
+ {
+ writer.Formatted();
+ }
+ }
+ else if (codedOutput is XmlFormatWriter)
+ {
+ XmlFormatWriter writer = (XmlFormatWriter)codedOutput;
+ if (options.FormattedOutput)
+ {
+ XmlWriterSettings settings = new XmlWriterSettings()
+ {
+ CheckCharacters = false,
+ NewLineHandling = NewLineHandling.Entitize,
+ OmitXmlDeclaration = true,
+ Encoding = new UTF8Encoding(false),
+ Indent = true,
+ IndentChars = " ",
+ };
+ // 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;
+ }
+
+ return codedOutput;
+ }
+
+ private static ICodedInputStream ContentTypeToInputStream(string contentType, MessageFormatOptions options, Stream input)
+ {
+ contentType = (contentType ?? String.Empty).Split(';')[0].Trim();
+
+ CodedInputBuilder factory;
+ if(!options.MimeInputTypesReadOnly.TryGetValue(contentType, out factory) || factory == null)
+ {
+ if(String.IsNullOrEmpty(options.DefaultContentType) ||
+ !options.MimeInputTypesReadOnly.TryGetValue(options.DefaultContentType, out factory) || factory == null)
+ {
+ throw new ArgumentOutOfRangeException("contentType");
+ }
+ }
+
+ return factory(input);
+ }
+
+ private static ICodedOutputStream ContentTypeToOutputStream(string contentType, MessageFormatOptions options, Stream output)
+ {
+ contentType = (contentType ?? String.Empty).Split(';')[0].Trim();
+
+ CodedOutputBuilder 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/csharp/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs b/csharp/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs
new file mode 100644
index 00000000..1480e50a
--- /dev/null
+++ b/csharp/src/ProtocolBuffers.Serialization/Http/MessageFormatOptions.cs
@@ -0,0 +1,176 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using Google.ProtocolBuffers.Collections;
+
+namespace Google.ProtocolBuffers.Serialization.Http
+{
+ /// <summary>
+ /// A delegate used to specify a method that constructs an ICodedInputStream from a .NET Stream.
+ /// </summary>
+ public delegate ICodedInputStream CodedInputBuilder(Stream stream);
+ /// <summary>
+ /// A delegate used to specify a method that constructs an ICodedOutputStream from a .NET Stream.
+ /// </summary>
+ public delegate ICodedOutputStream CodedOutputBuilder(Stream stream);
+
+ /// <summary>
+ /// Defines control information for the various formatting used with HTTP services
+ /// </summary>
+ public class MessageFormatOptions
+ {
+ /// <summary>The mime type for xml content</summary>
+ /// <remarks>Other valid xml mime types include: application/binary, application/x-protobuf</remarks>
+ public const string ContentTypeProtoBuffer = "application/vnd.google.protobuf";
+
+ /// <summary>The mime type for xml content</summary>
+ /// <remarks>Other valid xml mime types include: text/xml</remarks>
+ public const string ContentTypeXml = "application/xml";
+
+ /// <summary>The mime type for json content</summary>
+ /// <remarks>
+ /// Other valid json mime types include: application/json, application/x-json,
+ /// application/x-javascript, text/javascript, text/x-javascript, text/x-json, text/json
+ /// </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, CodedInputBuilder> MimeInputDefaults =
+ new ReadOnlyDictionary<string, CodedInputBuilder>(
+ new Dictionary<string, CodedInputBuilder>(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, CodedOutputBuilder> MimeOutputDefaults =
+ new ReadOnlyDictionary<string, CodedOutputBuilder>(
+ new Dictionary<string, CodedOutputBuilder>(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, CodedInputBuilder> _mimeInputTypes;
+ private Dictionary<string, CodedOutputBuilder> _mimeOutputTypes;
+
+ /// <summary> Provides access to modify the mime-type input stream construction </summary>
+ public IDictionary<string, CodedInputBuilder> MimeInputTypes
+ {
+ get
+ {
+ return _mimeInputTypes ??
+ (_mimeInputTypes = new Dictionary<string, CodedInputBuilder>(
+ MimeInputDefaults, StringComparer.OrdinalIgnoreCase));
+ }
+ }
+
+ /// <summary> Provides access to modify the mime-type input stream construction </summary>
+ public IDictionary<string, CodedOutputBuilder> MimeOutputTypes
+ {
+ get
+ {
+ return _mimeOutputTypes ??
+ (_mimeOutputTypes = new Dictionary<string, CodedOutputBuilder>(
+ MimeOutputDefaults, StringComparer.OrdinalIgnoreCase));
+ }
+ }
+
+ internal IDictionary<string, CodedInputBuilder> MimeInputTypesReadOnly
+ { get { return _mimeInputTypes ?? MimeInputDefaults; } }
+
+ internal IDictionary<string, CodedOutputBuilder> MimeOutputTypesReadOnly
+ { get { return _mimeOutputTypes ?? MimeOutputDefaults; } }
+
+ /// <summary>
+ /// The default content type to use if the input type is null or empty. If this
+ /// value is not supplied an ArgumentOutOfRangeException exception will be raised.
+ /// </summary>
+ public string DefaultContentType
+ {
+ get { return _defaultContentType ?? String.Empty; }
+ set { _defaultContentType = value; }
+ }
+
+ /// <summary>
+ /// The extension registry to use when reading messages
+ /// </summary>
+ public ExtensionRegistry ExtensionRegistry
+ {
+ get { return _extensionRegistry ?? ExtensionRegistry.Empty; }
+ set { _extensionRegistry = value; }
+ }
+
+ /// <summary>
+ /// The name of the xml root element when reading messages
+ /// </summary>
+ public string XmlReaderRootElementName
+ {
+ get { return _xmlReaderRootElementName ?? XmlFormatReader.DefaultRootElementName; }
+ set { _xmlReaderRootElementName = value; }
+ }
+
+ /// <summary>
+ /// Xml reader options
+ /// </summary>
+ public XmlReaderOptions XmlReaderOptions { get; set; }
+
+ /// <summary>
+ /// True to use formatted output including new-lines and default indentation
+ /// </summary>
+ public bool FormattedOutput { get; set; }
+
+ /// <summary>
+ /// The name of the xml root element when writing messages
+ /// </summary>
+ public string XmlWriterRootElementName
+ {
+ get { return _xmlWriterRootElementName ?? XmlFormatWriter.DefaultRootElementName; }
+ set { _xmlWriterRootElementName = value; }
+ }
+
+ /// <summary>
+ /// Xml writer options
+ /// </summary>
+ public XmlWriterOptions XmlWriterOptions { get; set; }
+ }
+} \ No newline at end of file