| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System; |
| | | 5 | | using System.Diagnostics.Contracts; |
| | | 6 | | using System.Globalization; |
| | | 7 | | using System.IO; |
| | | 8 | | using System.Net.Http.Headers; |
| | | 9 | | using System.Text; |
| | | 10 | | using System.Threading.Tasks; |
| | | 11 | | using System.Xml; |
| | | 12 | | using CoreWCF.Diagnostics; |
| | | 13 | | using CoreWCF.Runtime; |
| | | 14 | | |
| | | 15 | | namespace CoreWCF.Channels |
| | | 16 | | { |
| | | 17 | | internal class TextMessageEncoderFactory : MessageEncoderFactory |
| | | 18 | | { |
| | | 19 | | private readonly TextMessageEncoder _messageEncoder; |
| | | 20 | | //internal static ContentEncoding[] Soap11Content = GetContentEncodingMap(MessageVersion.Soap11WSAddressing10); |
| | | 21 | | // I believe replacing Soap11WSAddressing10 with Soap11 should work |
| | 7 | 22 | | internal static ContentEncoding[] Soap11Content = GetContentEncodingMap(MessageVersion.Soap11); |
| | 7 | 23 | | internal static ContentEncoding[] Soap12Content = GetContentEncodingMap(MessageVersion.Soap12WSAddressing10); |
| | 7 | 24 | | internal static ContentEncoding[] SoapNoneContent = GetContentEncodingMap(MessageVersion.None); |
| | | 25 | | internal const string Soap11MediaType = "text/xml"; |
| | | 26 | | internal const string Soap12MediaType = "application/soap+xml"; |
| | | 27 | | private const string XmlMediaType = "application/xml"; |
| | | 28 | | |
| | 770 | 29 | | public TextMessageEncoderFactory(MessageVersion version, Encoding writeEncoding, int maxReadPoolSize, int maxWri |
| | | 30 | | { |
| | 770 | 31 | | _messageEncoder = new TextMessageEncoder(version, writeEncoding, maxReadPoolSize, maxWritePoolSize, quotas); |
| | 770 | 32 | | } |
| | | 33 | | |
| | | 34 | | public override MessageEncoder Encoder |
| | | 35 | | { |
| | 3882 | 36 | | get { return _messageEncoder; } |
| | | 37 | | } |
| | | 38 | | |
| | | 39 | | public override MessageVersion MessageVersion |
| | | 40 | | { |
| | 6 | 41 | | get { return _messageEncoder.MessageVersion; } |
| | | 42 | | } |
| | | 43 | | |
| | | 44 | | public int MaxWritePoolSize |
| | | 45 | | { |
| | 0 | 46 | | get { return _messageEncoder.MaxWritePoolSize; } |
| | | 47 | | } |
| | | 48 | | |
| | | 49 | | public int MaxReadPoolSize |
| | | 50 | | { |
| | 0 | 51 | | get { return _messageEncoder.MaxReadPoolSize; } |
| | | 52 | | } |
| | | 53 | | |
| | | 54 | | public static Encoding[] GetSupportedEncodings() |
| | | 55 | | { |
| | 21 | 56 | | Encoding[] supported = TextEncoderDefaults.SupportedEncodings; |
| | 21 | 57 | | Encoding[] enc = new Encoding[supported.Length]; |
| | 21 | 58 | | Array.Copy(supported, enc, supported.Length); |
| | 21 | 59 | | return enc; |
| | | 60 | | } |
| | | 61 | | |
| | | 62 | | public XmlDictionaryReaderQuotas ReaderQuotas |
| | | 63 | | { |
| | | 64 | | get |
| | | 65 | | { |
| | 0 | 66 | | return _messageEncoder.ReaderQuotas; |
| | | 67 | | } |
| | | 68 | | } |
| | | 69 | | |
| | | 70 | | internal static string GetMediaType(MessageVersion version) |
| | | 71 | | { |
| | | 72 | | string mediaType; |
| | 791 | 73 | | if (version.Envelope == EnvelopeVersion.Soap12) |
| | | 74 | | { |
| | 462 | 75 | | mediaType = Soap12MediaType; |
| | | 76 | | } |
| | 329 | 77 | | else if (version.Envelope == EnvelopeVersion.Soap11) |
| | | 78 | | { |
| | 318 | 79 | | mediaType = Soap11MediaType; |
| | | 80 | | } |
| | 11 | 81 | | else if (version.Envelope == EnvelopeVersion.None) |
| | | 82 | | { |
| | 11 | 83 | | mediaType = XmlMediaType; |
| | | 84 | | } |
| | | 85 | | else |
| | | 86 | | { |
| | 0 | 87 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( |
| | 0 | 88 | | SR.Format(SR.EnvelopeVersionNotSupported, version.Envelope))); |
| | | 89 | | } |
| | 791 | 90 | | return mediaType; |
| | | 91 | | } |
| | | 92 | | |
| | | 93 | | internal static string GetContentType(string mediaType, Encoding encoding) |
| | | 94 | | { |
| | 833 | 95 | | return string.Format(CultureInfo.InvariantCulture, "{0}; charset={1}", mediaType, TextEncoderDefaults.Encodi |
| | | 96 | | } |
| | | 97 | | |
| | | 98 | | private static ContentEncoding[] GetContentEncodingMap(MessageVersion version) |
| | | 99 | | { |
| | 21 | 100 | | Encoding[] readEncodings = GetSupportedEncodings(); |
| | 21 | 101 | | string media = GetMediaType(version); |
| | 21 | 102 | | ContentEncoding[] map = new ContentEncoding[readEncodings.Length]; |
| | 168 | 103 | | for (int i = 0; i < readEncodings.Length; i++) |
| | | 104 | | { |
| | 63 | 105 | | ContentEncoding contentEncoding = new ContentEncoding |
| | 63 | 106 | | { |
| | 63 | 107 | | contentType = GetContentType(media, readEncodings[i]), |
| | 63 | 108 | | encoding = readEncodings[i] |
| | 63 | 109 | | }; |
| | 63 | 110 | | map[i] = contentEncoding; |
| | | 111 | | } |
| | 21 | 112 | | return map; |
| | | 113 | | } |
| | | 114 | | |
| | | 115 | | internal static Encoding GetEncodingFromContentType(string contentType, ContentEncoding[] contentMap) |
| | | 116 | | { |
| | 3312 | 117 | | if (contentType == null) |
| | | 118 | | { |
| | 2711 | 119 | | return null; |
| | | 120 | | } |
| | | 121 | | |
| | | 122 | | // Check for known/expected content types |
| | 1226 | 123 | | for (int i = 0; i < contentMap.Length; i++) |
| | | 124 | | { |
| | 609 | 125 | | if (contentMap[i].contentType == contentType) |
| | | 126 | | { |
| | 597 | 127 | | return contentMap[i].encoding; |
| | | 128 | | } |
| | | 129 | | } |
| | | 130 | | |
| | | 131 | | // then some heuristic matches (since System.Mime.ContentType is a performance hit) |
| | | 132 | | // start by looking for a parameter. |
| | | 133 | | |
| | | 134 | | // If none exists, we don't have an encoding |
| | 4 | 135 | | int semiColonIndex = contentType.IndexOf(';'); |
| | 4 | 136 | | if (semiColonIndex == -1) |
| | | 137 | | { |
| | 1 | 138 | | return null; |
| | | 139 | | } |
| | | 140 | | |
| | | 141 | | // optimize for charset being the first parameter |
| | 3 | 142 | | int charsetValueIndex = -1; |
| | | 143 | | |
| | | 144 | | // for Indigo scenarios, we'll have "; charset=", so check for the c |
| | 3 | 145 | | if ((contentType.Length > semiColonIndex + 11) // need room for parameter + charset + '=' |
| | 3 | 146 | | && contentType[semiColonIndex + 2] == 'c' |
| | 3 | 147 | | && string.Compare("charset=", 0, contentType, semiColonIndex + 2, 8, StringComparison.OrdinalIgnoreCase) |
| | | 148 | | { |
| | 3 | 149 | | charsetValueIndex = semiColonIndex + 10; |
| | | 150 | | } |
| | | 151 | | else |
| | | 152 | | { |
| | | 153 | | // look for charset= somewhere else in the message |
| | 0 | 154 | | int paramIndex = contentType.IndexOf("charset=", semiColonIndex + 1, StringComparison.OrdinalIgnoreCase) |
| | 0 | 155 | | if (paramIndex != -1) |
| | | 156 | | { |
| | | 157 | | // validate there's only whitespace or semi-colons beforehand |
| | 0 | 158 | | for (int i = paramIndex - 1; i >= semiColonIndex; i--) |
| | | 159 | | { |
| | 0 | 160 | | if (contentType[i] == ';') |
| | | 161 | | { |
| | 0 | 162 | | charsetValueIndex = paramIndex + 8; |
| | 0 | 163 | | break; |
| | | 164 | | } |
| | | 165 | | |
| | 0 | 166 | | if (contentType[i] == '\n') |
| | | 167 | | { |
| | 0 | 168 | | if (i == semiColonIndex || contentType[i - 1] != '\r') |
| | | 169 | | { |
| | | 170 | | break; |
| | | 171 | | } |
| | | 172 | | |
| | 0 | 173 | | i--; |
| | 0 | 174 | | continue; |
| | | 175 | | } |
| | | 176 | | |
| | 0 | 177 | | if (contentType[i] != ' ' |
| | 0 | 178 | | && contentType[i] != '\t') |
| | | 179 | | { |
| | | 180 | | break; |
| | | 181 | | } |
| | | 182 | | } |
| | | 183 | | } |
| | | 184 | | } |
| | | 185 | | |
| | | 186 | | string charSet; |
| | | 187 | | Encoding enc; |
| | | 188 | | |
| | | 189 | | // we have a possible charset value. If it's easy to parse, do so |
| | 3 | 190 | | if (charsetValueIndex != -1) |
| | | 191 | | { |
| | | 192 | | // get the next semicolon |
| | 3 | 193 | | semiColonIndex = contentType.IndexOf(';', charsetValueIndex); |
| | 3 | 194 | | if (semiColonIndex == -1) |
| | | 195 | | { |
| | 2 | 196 | | charSet = contentType.Substring(charsetValueIndex); |
| | | 197 | | } |
| | | 198 | | else |
| | | 199 | | { |
| | 1 | 200 | | charSet = contentType.Substring(charsetValueIndex, semiColonIndex - charsetValueIndex); |
| | | 201 | | } |
| | | 202 | | |
| | | 203 | | // and some minimal quote stripping |
| | 3 | 204 | | if (charSet.Length > 2 && charSet[0] == '"' && charSet[charSet.Length - 1] == '"') |
| | | 205 | | { |
| | 1 | 206 | | charSet = charSet.Substring(1, charSet.Length - 2); |
| | | 207 | | } |
| | | 208 | | |
| | 3 | 209 | | if (TryGetEncodingFromCharSet(charSet, out enc)) |
| | | 210 | | { |
| | 3 | 211 | | return enc; |
| | | 212 | | } |
| | | 213 | | } |
| | | 214 | | |
| | | 215 | | // our quick heuristics failed. fall back to System.Net |
| | | 216 | | try |
| | | 217 | | { |
| | 0 | 218 | | MediaTypeHeaderValue parsedContentType = MediaTypeHeaderValue.Parse(contentType); |
| | 0 | 219 | | charSet = parsedContentType.CharSet; |
| | 0 | 220 | | } |
| | 0 | 221 | | catch (FormatException e) |
| | | 222 | | { |
| | 0 | 223 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ProtocolException(SR.EncoderBadContentType |
| | | 224 | | } |
| | | 225 | | |
| | 0 | 226 | | if (TryGetEncodingFromCharSet(charSet, out enc)) |
| | | 227 | | { |
| | 0 | 228 | | return enc; |
| | | 229 | | } |
| | | 230 | | |
| | 0 | 231 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ProtocolException(SR.Format(SR.EncoderUnrecogn |
| | | 232 | | } |
| | | 233 | | |
| | | 234 | | internal static bool TryGetEncodingFromCharSet(string charSet, out Encoding encoding) |
| | | 235 | | { |
| | 3 | 236 | | encoding = null; |
| | 3 | 237 | | if (charSet == null || charSet.Length == 0) |
| | | 238 | | { |
| | 0 | 239 | | return true; |
| | | 240 | | } |
| | | 241 | | |
| | 3 | 242 | | return TextEncoderDefaults.TryGetEncoding(charSet, out encoding); |
| | | 243 | | } |
| | | 244 | | |
| | | 245 | | private class TextMessageEncoder : MessageEncoder |
| | | 246 | | { |
| | | 247 | | |
| | | 248 | | // Double-checked locking pattern requires volatile for read/write synchronization |
| | | 249 | | private volatile SynchronizedPool<XmlDictionaryWriter> _streamedWriterPool; |
| | | 250 | | private volatile SynchronizedPool<XmlDictionaryReader> _streamedReaderPool; |
| | | 251 | | private volatile SynchronizedPool<UTF8BufferedMessageData> _bufferedReaderPool; |
| | | 252 | | private volatile SynchronizedPool<TextBufferedMessageWriter> _bufferedWriterPool; |
| | | 253 | | private volatile SynchronizedPool<RecycledMessageState> _recycledStatePool; |
| | | 254 | | private readonly string _contentType; |
| | | 255 | | private readonly string _mediaType; |
| | | 256 | | private readonly Encoding _writeEncoding; |
| | | 257 | | private readonly MessageVersion _version; |
| | | 258 | | private readonly bool _optimizeWriteForUTF8; |
| | | 259 | | private const int maxPooledXmlReadersPerMessage = 2; |
| | | 260 | | private readonly XmlDictionaryReaderQuotas _bufferedReadReaderQuotas; |
| | | 261 | | private readonly OnXmlDictionaryReaderClose _onStreamedReaderClose; |
| | | 262 | | private readonly ContentEncoding[] _contentEncodingMap; |
| | | 263 | | |
| | 770 | 264 | | public TextMessageEncoder(MessageVersion version, Encoding writeEncoding, int maxReadPoolSize, int maxWriteP |
| | | 265 | | { |
| | 770 | 266 | | if (writeEncoding == null) |
| | | 267 | | { |
| | 0 | 268 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(writeEncoding)); |
| | | 269 | | } |
| | | 270 | | |
| | 770 | 271 | | TextEncoderDefaults.ValidateEncoding(writeEncoding); |
| | 770 | 272 | | _writeEncoding = writeEncoding; |
| | 770 | 273 | | _optimizeWriteForUTF8 = IsUTF8Encoding(writeEncoding); |
| | | 274 | | |
| | 770 | 275 | | ThisLock = new object(); |
| | | 276 | | |
| | 770 | 277 | | _version = version ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(version)); |
| | 770 | 278 | | MaxReadPoolSize = maxReadPoolSize; |
| | 770 | 279 | | MaxWritePoolSize = maxWritePoolSize; |
| | | 280 | | |
| | 770 | 281 | | ReaderQuotas = new XmlDictionaryReaderQuotas(); |
| | 770 | 282 | | quotas.CopyTo(ReaderQuotas); |
| | | 283 | | |
| | 770 | 284 | | _bufferedReadReaderQuotas = EncoderHelpers.GetBufferedReadQuotas(ReaderQuotas); |
| | | 285 | | |
| | 770 | 286 | | _onStreamedReaderClose = new OnXmlDictionaryReaderClose(ReturnStreamedReader); |
| | | 287 | | |
| | 770 | 288 | | _mediaType = GetMediaType(version); |
| | 770 | 289 | | _contentType = GetContentType(_mediaType, writeEncoding); |
| | 770 | 290 | | if (version.Envelope == EnvelopeVersion.Soap12) |
| | | 291 | | { |
| | 455 | 292 | | _contentEncodingMap = Soap12Content; |
| | | 293 | | } |
| | 315 | 294 | | else if (version.Envelope == EnvelopeVersion.Soap11) |
| | | 295 | | { |
| | 311 | 296 | | _contentEncodingMap = Soap11Content; |
| | | 297 | | } |
| | 4 | 298 | | else if (version.Envelope == EnvelopeVersion.None) |
| | | 299 | | { |
| | 4 | 300 | | _contentEncodingMap = SoapNoneContent; |
| | | 301 | | } |
| | | 302 | | else |
| | | 303 | | { |
| | 0 | 304 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( |
| | 0 | 305 | | SR.Format(SR.EnvelopeVersionNotSupported, version.Envelope))); |
| | | 306 | | } |
| | | 307 | | } |
| | | 308 | | |
| | | 309 | | private static bool IsUTF8Encoding(Encoding encoding) |
| | | 310 | | { |
| | 770 | 311 | | return encoding.WebName == "utf-8"; |
| | | 312 | | } |
| | | 313 | | |
| | | 314 | | public override string ContentType |
| | | 315 | | { |
| | 1204 | 316 | | get { return _contentType; } |
| | | 317 | | } |
| | | 318 | | |
| | 530 | 319 | | public int MaxWritePoolSize { get; } |
| | | 320 | | |
| | 1018 | 321 | | public int MaxReadPoolSize { get; } |
| | | 322 | | |
| | 4079 | 323 | | public XmlDictionaryReaderQuotas ReaderQuotas { get; } |
| | | 324 | | |
| | | 325 | | public override string MediaType |
| | | 326 | | { |
| | 602 | 327 | | get { return _mediaType; } |
| | | 328 | | } |
| | | 329 | | |
| | | 330 | | public override MessageVersion MessageVersion |
| | | 331 | | { |
| | 2453 | 332 | | get { return _version; } |
| | | 333 | | } |
| | | 334 | | |
| | 1549 | 335 | | private object ThisLock { get; } |
| | | 336 | | |
| | | 337 | | |
| | | 338 | | protected override bool IsCharSetSupported(string charSet) |
| | | 339 | | { |
| | | 340 | | Encoding tmp; |
| | 2 | 341 | | if (!TextEncoderDefaults.TryGetEncoding(charSet, out tmp)) |
| | | 342 | | { |
| | | 343 | | // GetEncodingFromContentType supports charset with quotes (by simply stripping them) so we do the s |
| | | 344 | | // This also gives us parity with Desktop WCF behavior |
| | 1 | 345 | | if (charSet.Length > 2 && charSet[0] == '"' && charSet[charSet.Length - 1] == '"') |
| | | 346 | | { |
| | 1 | 347 | | charSet = charSet.Substring(1, charSet.Length - 2); |
| | 1 | 348 | | return TextEncoderDefaults.TryGetEncoding(charSet, out tmp); |
| | | 349 | | } |
| | 0 | 350 | | return false; |
| | | 351 | | } |
| | | 352 | | |
| | 1 | 353 | | return true; |
| | | 354 | | } |
| | | 355 | | |
| | | 356 | | public override bool IsContentTypeSupported(string contentType) |
| | | 357 | | { |
| | 602 | 358 | | if (contentType == null) |
| | | 359 | | { |
| | 0 | 360 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(contentType)); |
| | | 361 | | } |
| | | 362 | | |
| | 602 | 363 | | if (base.IsContentTypeSupported(contentType)) |
| | | 364 | | { |
| | 599 | 365 | | return true; |
| | | 366 | | } |
| | | 367 | | |
| | | 368 | | // we support a few extra content types for "none" |
| | 3 | 369 | | if (MessageVersion == MessageVersion.None) |
| | | 370 | | { |
| | | 371 | | const string rss1MediaType = "text/xml"; |
| | | 372 | | const string rss2MediaType = "application/rss+xml"; |
| | | 373 | | const string atomMediaType = "application/atom+xml"; |
| | | 374 | | const string htmlMediaType = "text/html"; |
| | | 375 | | |
| | 2 | 376 | | if (IsContentTypeSupported(contentType, rss1MediaType, rss1MediaType)) |
| | | 377 | | { |
| | 1 | 378 | | return true; |
| | | 379 | | } |
| | 1 | 380 | | if (IsContentTypeSupported(contentType, rss2MediaType, rss2MediaType)) |
| | | 381 | | { |
| | 0 | 382 | | return true; |
| | | 383 | | } |
| | 1 | 384 | | if (IsContentTypeSupported(contentType, htmlMediaType, htmlMediaType)) |
| | | 385 | | { |
| | 0 | 386 | | return true; |
| | | 387 | | } |
| | 1 | 388 | | if (IsContentTypeSupported(contentType, atomMediaType, atomMediaType)) |
| | | 389 | | { |
| | 0 | 390 | | return true; |
| | | 391 | | } |
| | | 392 | | // application/xml checked by base method |
| | | 393 | | } |
| | | 394 | | |
| | 2 | 395 | | return false; |
| | | 396 | | } |
| | | 397 | | |
| | | 398 | | public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentTy |
| | | 399 | | { |
| | 773 | 400 | | if (bufferManager == null) |
| | | 401 | | { |
| | 0 | 402 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(bufferManager)); |
| | | 403 | | } |
| | | 404 | | |
| | | 405 | | Message message; |
| | | 406 | | |
| | 773 | 407 | | UTF8BufferedMessageData messageData = TakeBufferedReader(); |
| | 773 | 408 | | messageData.Encoding = GetEncodingFromContentType(contentType, _contentEncodingMap); |
| | 773 | 409 | | messageData.Open(buffer, bufferManager); |
| | 773 | 410 | | RecycledMessageState messageState = messageData.TakeMessageState(); |
| | 773 | 411 | | if (messageState == null) |
| | | 412 | | { |
| | 531 | 413 | | messageState = new RecycledMessageState(); |
| | | 414 | | } |
| | | 415 | | |
| | 773 | 416 | | message = new BufferedMessage(messageData, messageState); |
| | | 417 | | |
| | 773 | 418 | | message.Properties.Encoder = this; |
| | | 419 | | |
| | 773 | 420 | | return message; |
| | | 421 | | } |
| | | 422 | | |
| | | 423 | | public override Task<Message> ReadMessageAsync(Stream stream, int maxSizeOfHeaders, string contentType) |
| | | 424 | | { |
| | 2539 | 425 | | if (stream == null) |
| | | 426 | | { |
| | 0 | 427 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException(nameof(stream))) |
| | | 428 | | } |
| | | 429 | | |
| | 2539 | 430 | | XmlReader reader = TakeStreamedReader(stream, GetEncodingFromContentType(contentType, _contentEncodingMa |
| | 2537 | 431 | | Message message = Message.CreateMessage(reader, maxSizeOfHeaders, _version); |
| | 2524 | 432 | | message.Properties.Encoder = this; |
| | | 433 | | |
| | 2524 | 434 | | return Task.FromResult(message); |
| | | 435 | | } |
| | | 436 | | |
| | | 437 | | public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferMan |
| | | 438 | | { |
| | 666 | 439 | | if (message == null) |
| | | 440 | | { |
| | 0 | 441 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException(nameof(message)) |
| | | 442 | | } |
| | | 443 | | |
| | 666 | 444 | | if (bufferManager == null) |
| | | 445 | | { |
| | 0 | 446 | | throw TraceUtility.ThrowHelperError(new ArgumentNullException(nameof(bufferManager)), message); |
| | | 447 | | } |
| | | 448 | | |
| | 666 | 449 | | if (maxMessageSize < 0) |
| | | 450 | | { |
| | 0 | 451 | | throw TraceUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(maxMessageSize), maxMessa |
| | 0 | 452 | | SRCommon.ValueMustBeNonNegative), message); |
| | | 453 | | } |
| | | 454 | | |
| | 666 | 455 | | if (messageOffset < 0 || messageOffset > maxMessageSize) |
| | | 456 | | { |
| | 0 | 457 | | throw TraceUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(messageOffset), messageOf |
| | 0 | 458 | | SR.Format(SR.ValueMustBeInRange, 0, maxMessageSize)), message); |
| | | 459 | | } |
| | | 460 | | |
| | 666 | 461 | | ThrowIfMismatchedMessageVersion(message); |
| | | 462 | | |
| | 666 | 463 | | message.Properties.Encoder = this; |
| | 666 | 464 | | TextBufferedMessageWriter messageWriter = TakeBufferedWriter(); |
| | 666 | 465 | | ArraySegment<byte> messageData = messageWriter.WriteMessage(message, bufferManager, messageOffset, maxMe |
| | 666 | 466 | | ReturnMessageWriter(messageWriter); |
| | | 467 | | |
| | 666 | 468 | | return messageData; |
| | | 469 | | } |
| | | 470 | | |
| | | 471 | | public override async Task WriteMessageAsync(Message message, Stream stream) |
| | | 472 | | { |
| | 826 | 473 | | if (message == null) |
| | | 474 | | { |
| | 0 | 475 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(message)); |
| | | 476 | | } |
| | | 477 | | |
| | 826 | 478 | | if (stream == null) |
| | | 479 | | { |
| | 0 | 480 | | throw TraceUtility.ThrowHelperError(new ArgumentNullException(nameof(stream)), message); |
| | | 481 | | } |
| | | 482 | | |
| | 826 | 483 | | ThrowIfMismatchedMessageVersion(message); |
| | | 484 | | |
| | 826 | 485 | | message.Properties.Encoder = this; |
| | 826 | 486 | | XmlDictionaryWriter xmlWriter = TakeStreamedWriter(stream); |
| | 826 | 487 | | if (_optimizeWriteForUTF8) |
| | | 488 | | { |
| | 826 | 489 | | await message.WriteMessageAsync(xmlWriter); |
| | | 490 | | } |
| | | 491 | | else |
| | | 492 | | { |
| | 0 | 493 | | xmlWriter.WriteStartDocument(); |
| | 0 | 494 | | await message.WriteMessageAsync(xmlWriter); |
| | 0 | 495 | | xmlWriter.WriteEndDocument(); |
| | | 496 | | } |
| | | 497 | | |
| | 825 | 498 | | xmlWriter.Flush(); |
| | 825 | 499 | | ReturnStreamedWriter(xmlWriter); |
| | 825 | 500 | | await stream.FlushAsync(); |
| | 825 | 501 | | } |
| | | 502 | | |
| | | 503 | | private XmlDictionaryWriter TakeStreamedWriter(Stream stream) |
| | | 504 | | { |
| | 826 | 505 | | if (_streamedWriterPool == null) |
| | | 506 | | { |
| | 101 | 507 | | lock (ThisLock) |
| | | 508 | | { |
| | 101 | 509 | | if (_streamedWriterPool == null) |
| | | 510 | | { |
| | 101 | 511 | | _streamedWriterPool = new SynchronizedPool<XmlDictionaryWriter>(MaxWritePoolSize); |
| | | 512 | | } |
| | 101 | 513 | | } |
| | | 514 | | } |
| | 826 | 515 | | XmlDictionaryWriter xmlWriter = _streamedWriterPool.Take(); |
| | 826 | 516 | | if (xmlWriter == null) |
| | | 517 | | { |
| | 826 | 518 | | xmlWriter = XmlDictionaryWriter.CreateTextWriter(stream, _writeEncoding, false); |
| | | 519 | | } |
| | | 520 | | // TODO: Use the reinitialization API's once moved to .Net Standard 2.0 |
| | | 521 | | //else |
| | | 522 | | //{ |
| | | 523 | | // ((IXmlTextWriterInitializer)xmlWriter).SetOutput(stream, this.writeEncoding, false); |
| | | 524 | | //} |
| | 826 | 525 | | return xmlWriter; |
| | | 526 | | } |
| | | 527 | | |
| | | 528 | | private void ReturnStreamedWriter(XmlWriter xmlWriter) |
| | | 529 | | { |
| | 825 | 530 | | xmlWriter.Dispose(); |
| | | 531 | | // TODO: Use the reinitialization API's once moved to .Net Standard 2.0 |
| | | 532 | | //streamedWriterPool.Return((XmlDictionaryWriter)xmlWriter); |
| | 825 | 533 | | } |
| | | 534 | | |
| | | 535 | | private TextBufferedMessageWriter TakeBufferedWriter() |
| | | 536 | | { |
| | 666 | 537 | | if (_bufferedWriterPool == null) |
| | | 538 | | { |
| | 430 | 539 | | lock (ThisLock) |
| | | 540 | | { |
| | 430 | 541 | | if (_bufferedWriterPool == null) |
| | | 542 | | { |
| | 429 | 543 | | _bufferedWriterPool = new SynchronizedPool<TextBufferedMessageWriter>(MaxWritePoolSize); |
| | | 544 | | } |
| | 430 | 545 | | } |
| | | 546 | | } |
| | | 547 | | |
| | 666 | 548 | | TextBufferedMessageWriter messageWriter = _bufferedWriterPool.Take(); |
| | 666 | 549 | | if (messageWriter == null) |
| | | 550 | | { |
| | 431 | 551 | | messageWriter = new TextBufferedMessageWriter(this); |
| | | 552 | | } |
| | 666 | 553 | | return messageWriter; |
| | | 554 | | } |
| | | 555 | | |
| | | 556 | | private void ReturnMessageWriter(TextBufferedMessageWriter messageWriter) |
| | | 557 | | { |
| | 666 | 558 | | _bufferedWriterPool.Return(messageWriter); |
| | 666 | 559 | | } |
| | | 560 | | |
| | | 561 | | private XmlReader TakeStreamedReader(Stream stream, Encoding enc) |
| | | 562 | | { |
| | 2539 | 563 | | if (_streamedReaderPool == null) |
| | | 564 | | { |
| | 144 | 565 | | lock (ThisLock) |
| | | 566 | | { |
| | 144 | 567 | | if (_streamedReaderPool == null) |
| | | 568 | | { |
| | 144 | 569 | | _streamedReaderPool = new SynchronizedPool<XmlDictionaryReader>(MaxReadPoolSize); |
| | | 570 | | } |
| | 144 | 571 | | } |
| | | 572 | | } |
| | 2539 | 573 | | XmlDictionaryReader xmlReader = _streamedReaderPool.Take(); |
| | 2539 | 574 | | if (xmlReader == null) |
| | | 575 | | { |
| | 2539 | 576 | | xmlReader = XmlDictionaryReader.CreateTextReader(stream, enc, ReaderQuotas, null); |
| | | 577 | | } |
| | | 578 | | else |
| | | 579 | | { |
| | 0 | 580 | | ((IXmlTextReaderInitializer)xmlReader).SetInput(stream, enc, ReaderQuotas, _onStreamedReaderClose); |
| | | 581 | | } |
| | 2537 | 582 | | return xmlReader; |
| | | 583 | | } |
| | | 584 | | |
| | | 585 | | private void ReturnStreamedReader(XmlDictionaryReader xmlReader) |
| | | 586 | | { |
| | 0 | 587 | | _streamedReaderPool.Return(xmlReader); |
| | 0 | 588 | | } |
| | | 589 | | |
| | | 590 | | private XmlDictionaryWriter CreateWriter(Stream stream) |
| | | 591 | | { |
| | 0 | 592 | | return XmlDictionaryWriter.CreateTextWriter(stream, _writeEncoding, false); |
| | | 593 | | } |
| | | 594 | | |
| | | 595 | | private UTF8BufferedMessageData TakeBufferedReader() |
| | | 596 | | { |
| | 773 | 597 | | if (_bufferedReaderPool == null) |
| | | 598 | | { |
| | 437 | 599 | | lock (ThisLock) |
| | | 600 | | { |
| | 437 | 601 | | if (_bufferedReaderPool == null) |
| | | 602 | | { |
| | 437 | 603 | | _bufferedReaderPool = new SynchronizedPool<UTF8BufferedMessageData>(MaxReadPoolSize); |
| | | 604 | | } |
| | 437 | 605 | | } |
| | | 606 | | } |
| | 773 | 607 | | UTF8BufferedMessageData messageData = _bufferedReaderPool.Take(); |
| | 773 | 608 | | if (messageData == null) |
| | | 609 | | { |
| | 531 | 610 | | messageData = new UTF8BufferedMessageData(this, maxPooledXmlReadersPerMessage); |
| | | 611 | | } |
| | 773 | 612 | | return messageData; |
| | | 613 | | } |
| | | 614 | | |
| | | 615 | | private void ReturnBufferedData(UTF8BufferedMessageData messageData) |
| | | 616 | | { |
| | 506 | 617 | | _bufferedReaderPool.Return(messageData); |
| | 506 | 618 | | } |
| | | 619 | | |
| | | 620 | | private SynchronizedPool<RecycledMessageState> RecycledStatePool |
| | | 621 | | { |
| | | 622 | | get |
| | | 623 | | { |
| | 531 | 624 | | if (_recycledStatePool == null) |
| | | 625 | | { |
| | 437 | 626 | | lock (ThisLock) |
| | | 627 | | { |
| | 437 | 628 | | if (_recycledStatePool == null) |
| | | 629 | | { |
| | 437 | 630 | | _recycledStatePool = new SynchronizedPool<RecycledMessageState>(MaxReadPoolSize); |
| | | 631 | | } |
| | 437 | 632 | | } |
| | | 633 | | } |
| | 531 | 634 | | return _recycledStatePool; |
| | | 635 | | } |
| | | 636 | | } |
| | | 637 | | |
| | | 638 | | private class UTF8BufferedMessageData : BufferedMessageData |
| | | 639 | | { |
| | | 640 | | private readonly TextMessageEncoder _messageEncoder; |
| | | 641 | | private readonly Pool<XmlDictionaryReader> _readerPool; |
| | | 642 | | private readonly OnXmlDictionaryReaderClose _onClose; |
| | | 643 | | private Encoding _encoding; |
| | | 644 | | private const int additionalNodeSpace = 1024; |
| | | 645 | | |
| | | 646 | | public UTF8BufferedMessageData(TextMessageEncoder messageEncoder, int maxReaderPoolSize) |
| | 531 | 647 | | : base(messageEncoder.RecycledStatePool) |
| | | 648 | | { |
| | 531 | 649 | | _messageEncoder = messageEncoder; |
| | 531 | 650 | | _readerPool = new Pool<XmlDictionaryReader>(maxReaderPoolSize); |
| | 531 | 651 | | _onClose = new OnXmlDictionaryReaderClose(OnXmlReaderClosed); |
| | 531 | 652 | | } |
| | | 653 | | |
| | | 654 | | internal Encoding Encoding |
| | | 655 | | { |
| | | 656 | | set |
| | | 657 | | { |
| | 773 | 658 | | _encoding = value; |
| | 773 | 659 | | } |
| | | 660 | | } |
| | | 661 | | |
| | | 662 | | public override MessageEncoder MessageEncoder |
| | | 663 | | { |
| | 951 | 664 | | get { return _messageEncoder; } |
| | | 665 | | } |
| | | 666 | | |
| | | 667 | | public override XmlDictionaryReaderQuotas Quotas |
| | | 668 | | { |
| | 2146 | 669 | | get { return _messageEncoder._bufferedReadReaderQuotas; } |
| | | 670 | | } |
| | | 671 | | |
| | | 672 | | protected override void OnClosed() |
| | | 673 | | { |
| | 506 | 674 | | _messageEncoder.ReturnBufferedData(this); |
| | 506 | 675 | | } |
| | | 676 | | |
| | | 677 | | protected override XmlDictionaryReader TakeXmlReader() |
| | | 678 | | { |
| | 2146 | 679 | | ArraySegment<byte> buffer = Buffer; |
| | | 680 | | |
| | 2146 | 681 | | XmlDictionaryReader xmlReader = _readerPool.Take(); |
| | 2146 | 682 | | if (xmlReader == null) |
| | | 683 | | { |
| | | 684 | | // TODO: Use the reinitialization API's once moved to .Net Standard 2.0 |
| | 955 | 685 | | xmlReader = XmlDictionaryReader.CreateTextReader(buffer.Array, buffer.Offset, buffer.Count, _enc |
| | | 686 | | } |
| | | 687 | | else |
| | | 688 | | { |
| | 1191 | 689 | | ((IXmlTextReaderInitializer)xmlReader).SetInput(buffer.Array, buffer.Offset, buffer.Count, _enco |
| | | 690 | | } |
| | | 691 | | |
| | 2146 | 692 | | return xmlReader; |
| | | 693 | | } |
| | | 694 | | |
| | | 695 | | protected override void ReturnXmlReader(XmlDictionaryReader xmlReader) |
| | | 696 | | { |
| | 1857 | 697 | | if (xmlReader != null) |
| | | 698 | | { |
| | 1857 | 699 | | _readerPool.Return(xmlReader); |
| | | 700 | | } |
| | 1857 | 701 | | } |
| | | 702 | | } |
| | | 703 | | |
| | | 704 | | private class TextBufferedMessageWriter : BufferedMessageWriter |
| | | 705 | | { |
| | | 706 | | private readonly TextMessageEncoder _messageEncoder; |
| | | 707 | | //XmlDictionaryWriter writer; |
| | | 708 | | |
| | 431 | 709 | | public TextBufferedMessageWriter(TextMessageEncoder messageEncoder) |
| | | 710 | | { |
| | 431 | 711 | | _messageEncoder = messageEncoder; |
| | 431 | 712 | | } |
| | | 713 | | |
| | | 714 | | protected override void OnWriteStartMessage(XmlDictionaryWriter writer) |
| | | 715 | | { |
| | 666 | 716 | | if (!_messageEncoder._optimizeWriteForUTF8) |
| | | 717 | | { |
| | 0 | 718 | | writer.WriteStartDocument(); |
| | | 719 | | } |
| | 666 | 720 | | } |
| | | 721 | | |
| | | 722 | | protected override void OnWriteEndMessage(XmlDictionaryWriter writer) |
| | | 723 | | { |
| | 666 | 724 | | if (!_messageEncoder._optimizeWriteForUTF8) |
| | | 725 | | { |
| | 0 | 726 | | writer.WriteEndDocument(); |
| | | 727 | | } |
| | 666 | 728 | | } |
| | | 729 | | |
| | | 730 | | protected override XmlDictionaryWriter TakeXmlWriter(Stream stream) |
| | | 731 | | { |
| | 666 | 732 | | if (_messageEncoder._optimizeWriteForUTF8) |
| | | 733 | | { |
| | | 734 | | //XmlDictionaryWriter returnedWriter = writer; |
| | | 735 | | //if (returnedWriter == null) |
| | | 736 | | //{ |
| | | 737 | | // returnedWriter = XmlDictionaryWriter.CreateTextWriter(stream, messageEncoder.writeEncoding |
| | | 738 | | //} |
| | | 739 | | //else |
| | | 740 | | //{ |
| | | 741 | | // writer = null; |
| | | 742 | | // ((IXmlTextWriterInitializer)returnedWriter).SetOutput(stream, messageEncoder.writeEncoding |
| | | 743 | | //} |
| | | 744 | | //return returnedWriter; |
| | | 745 | | // TODO: Use IXmlTextWriterInitializer when moved to .Net Standard 2.0 |
| | 666 | 746 | | return XmlDictionaryWriter.CreateTextWriter(stream, _messageEncoder._writeEncoding, false); |
| | | 747 | | } |
| | | 748 | | else |
| | | 749 | | { |
| | 0 | 750 | | return _messageEncoder.CreateWriter(stream); |
| | | 751 | | } |
| | | 752 | | } |
| | | 753 | | |
| | | 754 | | protected override void ReturnXmlWriter(XmlDictionaryWriter writer) |
| | | 755 | | { |
| | | 756 | | Contract.Assert(writer != null, "writer MUST NOT be null"); |
| | 666 | 757 | | writer.Flush(); |
| | 666 | 758 | | writer.Dispose(); |
| | | 759 | | |
| | | 760 | | // TODO: Use IXmlTextWriterInitializer reuse once moved to .Net Standard 2.0 |
| | | 761 | | //if (messageEncoder.optimizeWriteForUTF8) |
| | | 762 | | //{ |
| | | 763 | | // if (this.writer == null) |
| | | 764 | | // this.writer = writer; |
| | | 765 | | //} |
| | 666 | 766 | | } |
| | | 767 | | } |
| | | 768 | | } |
| | | 769 | | } |
| | | 770 | | |
| | | 771 | | internal class ContentEncoding |
| | | 772 | | { |
| | | 773 | | public string contentType; |
| | | 774 | | public Encoding encoding; |
| | | 775 | | } |
| | | 776 | | } |