Merge pull request #1668 from alexanderkyte/bug1856
[mono.git] / mcs / class / System.ServiceModel.Web / System.ServiceModel.Channels / WebMessageEncoder.cs
1 //
2 // WebMessageEncoder.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //
7 // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.IO;
30 using System.Runtime.Serialization.Json;
31 using System.ServiceModel;
32 using System.ServiceModel.Dispatcher;
33 using System.Text;
34 using System.Xml;
35
36 namespace System.ServiceModel.Channels
37 {
38         internal class WebMessageEncoder : MessageEncoder
39         {
40                 internal const string ScriptPropertyName = "618BC2B0-38AA-21A3-DB4A-404FC87B9B11"; // randomly generated
41
42                 WebMessageEncodingBindingElement source;
43
44                 public WebMessageEncoder (WebMessageEncodingBindingElement source)
45                 {
46                         this.source = source;
47                 }
48
49                 public override string ContentType {
50 #if NET_2_1
51                         get { return MediaType; }
52 #else
53                         get { return MediaType + "; charset=" + source.WriteEncoding.HeaderName; }
54 #endif
55                 }
56
57                 // FIXME: find out how it can be customized.
58                 public override string MediaType {
59                         get { return "application/xml"; }
60                 }
61
62                 public override MessageVersion MessageVersion {
63                         get { return MessageVersion.None; }
64                 }
65
66                 public override bool IsContentTypeSupported (string contentType)
67                 {
68                         if (contentType == null)
69                                 throw new ArgumentNullException ("contentType");
70                         return true; // anything is accepted.
71                 }
72
73                 public override Message ReadMessage (ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
74                 {
75                         throw new NotImplementedException ();
76                 }
77
78                 public override Message ReadMessage (Stream stream, int maxSizeOfHeaders, string contentType)
79                 {
80                         if (stream == null)
81                                 throw new ArgumentNullException ("stream");
82
83                         contentType = contentType ?? "application/octet-stream";
84
85                         Encoding enc = Encoding.UTF8;
86                         var ct = new System.Net.Mime.ContentType (contentType);
87                         if (ct.CharSet != null)
88                                 enc = Encoding.GetEncoding (ct.CharSet);
89
90                         WebContentFormat fmt = WebContentFormat.Xml;
91                         if (source.ContentTypeMapper != null)
92                                 fmt = source.ContentTypeMapper.GetMessageFormatForContentType (contentType);
93                         else {
94                                 switch (ct.MediaType) {
95                                 case "application/json":
96                                         fmt = WebContentFormat.Json;
97                                         break;
98                                 case "application/xml":
99                                         fmt = WebContentFormat.Xml;
100                                         break;
101                                 default:
102                                         fmt = WebContentFormat.Raw;
103                                         break;
104                                 }
105                         }
106
107                         Message msg = null;
108                         WebBodyFormatMessageProperty wp = null;
109                         switch (fmt) {
110                         case WebContentFormat.Xml:
111                                 // FIXME: is it safe/unsafe/required to keep XmlReader open?
112                                 msg = Message.CreateMessage (MessageVersion.None, null, XmlReader.Create (new StreamReader (stream, enc)));
113                                 wp = new WebBodyFormatMessageProperty (WebContentFormat.Xml);
114                                 break;
115                         case WebContentFormat.Json:
116                                 // FIXME: is it safe/unsafe/required to keep XmlReader open?
117 #if NET_2_1
118                                 msg = Message.CreateMessage (MessageVersion.None, null, JsonReaderWriterFactory.CreateJsonReader (stream, source.ReaderQuotas));
119 #else
120                                 msg = Message.CreateMessage (MessageVersion.None, null, JsonReaderWriterFactory.CreateJsonReader (stream, enc, source.ReaderQuotas, null));
121 #endif
122                                 wp = new WebBodyFormatMessageProperty (WebContentFormat.Json);
123                                 break;
124                         case WebContentFormat.Raw:
125                                 msg = new WebMessageFormatter.RawMessage (stream);
126                                 wp = new WebBodyFormatMessageProperty (WebContentFormat.Raw);
127                                 break;
128                         default:
129                                 throw new SystemException ("INTERNAL ERROR: cannot determine content format");
130                         }
131                         if (wp != null)
132                                 msg.Properties.Add (WebBodyFormatMessageProperty.Name, wp);
133                         msg.Properties.Encoder = this;
134                         return msg;
135                 }
136
137                 WebContentFormat GetContentFormat (Message message)
138                 {
139                         string name = WebBodyFormatMessageProperty.Name;
140                         if (message.Properties.ContainsKey (name))
141                                 return ((WebBodyFormatMessageProperty) message.Properties [name]).Format;
142
143                         switch (MediaType) {
144                         case "application/xml":
145                         case "text/xml":
146                                 return WebContentFormat.Xml;
147                         case "application/json":
148                         case "text/json":
149                                 return WebContentFormat.Json;
150                         case "application/octet-stream":
151                                 return WebContentFormat.Raw;
152                         default:
153                                 return WebContentFormat.Default;
154                         }
155                 }
156
157                 public override void WriteMessage (Message message, Stream stream)
158                 {
159                         if (message == null)
160                                 throw new ArgumentNullException ("message");
161
162                         // Handle /js and /jsdebug as the special cases.
163                         var script = message.Properties [ScriptPropertyName] as string;
164                         if (script != null) {
165                                 var bytes = source.WriteEncoding.GetBytes (script);
166                                 stream.Write (bytes, 0, bytes.Length);
167                                 return;
168                         }
169
170                         if (!MessageVersion.Equals (message.Version))
171                                 throw new ProtocolException (String.Format ("MessageVersion {0} is not supported", message.Version));
172                         if (stream == null)
173                                 throw new ArgumentNullException ("stream");
174
175                         switch (GetContentFormat (message)) {
176                         case WebContentFormat.Xml:
177 #if NET_2_1
178                                 using (XmlWriter w = XmlDictionaryWriter.CreateDictionaryWriter (XmlWriter.Create (new StreamWriter (stream, source.WriteEncoding))))
179                                         message.WriteMessage (w);
180 #else
181                                 using (XmlWriter w = XmlDictionaryWriter.CreateTextWriter (stream, source.WriteEncoding, false))
182                                         message.WriteMessage (w);
183 #endif
184                                 break;
185                         case WebContentFormat.Json:
186                                 using (XmlWriter w = JsonReaderWriterFactory.CreateJsonWriter (stream, source.WriteEncoding, false))
187                                         message.WriteMessage (w);
188                                 break;
189                         case WebContentFormat.Raw:
190                                 var rmsg = (WebMessageFormatter.RawMessage) message;
191                                 var src = rmsg.Stream;
192                                 if (src == null) // null output
193                                         break;
194
195                                 int len = 0;
196                                 byte [] buffer = new byte [4096];
197                                 while ((len = src.Read (buffer, 0, buffer.Length)) > 0)
198                                         stream.Write (buffer, 0, len);
199                                 break;
200                         case WebContentFormat.Default:
201                                 throw new SystemException ("INTERNAL ERROR: cannot determine content format");
202                         }
203                 }
204
205                 public override ArraySegment<byte> WriteMessage (Message message, int maxMessageSize, BufferManager bufferManager,
206                                                                  int messageOffset)
207                 {
208                         throw new NotImplementedException ();
209                 }
210         }
211 }