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 
WebMessageEncoder(WebMessageEncodingBindingElement source)44 		public WebMessageEncoder (WebMessageEncodingBindingElement source)
45 		{
46 			this.source = source;
47 		}
48 
49 		public override string ContentType {
50 #if MOBILE
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 
IsContentTypeSupported(string contentType)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 
ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)73 		public override Message ReadMessage (ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
74 		{
75 			throw new NotImplementedException ();
76 		}
77 
ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)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 MOBILE
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 
GetContentFormat(Message message)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 
WriteMessage(Message message, Stream stream)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 MOBILE
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 
WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)205 		public override ArraySegment<byte> WriteMessage (Message message, int maxMessageSize, BufferManager bufferManager,
206 								 int messageOffset)
207 		{
208 			throw new NotImplementedException ();
209 		}
210 	}
211 }
212