/// <summary> /// Writes the error with fallback logic for XML cases where the writer is in an error state and a new writer must be created. /// </summary> /// <param name="messageWriter">The message writer.</param> /// <param name="encoding">The encoding to use for the error if we have to fallback.</param> /// <param name="responseStream">The response stream to write to in the fallback case.</param> /// <param name="args">The args for the error.</param> /// <param name="error">The error to write.</param> /// <param name="messageWriterBuilder">MessageWriterBuilder to use if a new ODataMessageWriter needs to be constructed.</param> private static void WriteErrorWithFallbackForXml(ODataMessageWriter messageWriter, Encoding encoding, Stream responseStream, HandleExceptionArgs args, ODataError error, MessageWriterBuilder messageWriterBuilder) { Debug.Assert(args != null, "args != null"); #if DEBUG Debug.Assert(args.ProcessExceptionWasCalled, "ProcessException was not called by the time we tried to serialze this error message with ODataLib."); #endif if (messageWriter != null) { try { // If the XmlWriter inside the ODataMessageWriter had entered Error state, ODataMessageWriter.WriteError would throw an InvalidOperationException // when we try to write to it. Note that XmlWriter doesn't always throw an XmlException when it enters Error state. // The right thing to do is we don't write any more because at this point we don't know what's been written to the underlying // stream. However we still should flush the writer to make sure that all the content that was written but is sitting in the buffers actually appears // in the stream before writing the instream error. Otherwise the buffer will be flushed when disposing the writer later and we would end up with // either content written after the instream error (this would also result in having the Xml declaration in the middle of the payload - // [Astoria-ODataLib-Integration] In-stream errors due to XmlExceptions are written out backwards (error before partial valid payload)) or, // hypothetically, the instream error in the middle of the other content that was already partially written. For example we can end up with a payload that // looks like <element attr="val<m:error... The XmlReader would not be able to parse the error payload in this case. Disposing the writer will flush the buffer. // It is fine to do it since the writer is not usable at this point anyways. Also note that the writer will be disposed more than once (e.g. in finally block // in ResponseBodySerializer) but only the first call will have any effect. // However since in the versions we shipped we always create a new XmlWriter to serialize the error payload when the existing // one is in error state, we will continue to do the same to avoid introducing any breaking change here. messageWriter.WriteError(error, args.UseVerboseErrors); } catch (ODataException e) { // Yikes, ODataLib threw while writing the error. This tends to happen if the service author did something invalid during custom // error handling, such as add an custom instance annotation to the error payload. In this dire case, we treat it almost like // an in-stream error, and abort the previous writing. We write out the new error. Note that this will produce an invalid payload like // the situation noted above with XmlWriter errors. WebUtil.Dispose(messageWriter); messageWriterBuilder.SetMessageForErrorInError(); var newErrorWriter = messageWriterBuilder.CreateWriter(); ODataError errorWhileWritingOtherError = new ODataError() { ErrorCode = "500", InnerError = new ODataInnerError(e), Message = Strings.ErrorHandler_ErrorWhileWritingError }; newErrorWriter.WriteError(errorWhileWritingOtherError, args.UseVerboseErrors); } catch (InvalidOperationException) { Debug.Assert(ContentTypeUtil.IsNotJson(args.ResponseContentType), "Should never get here for JSON responses"); WebUtil.Dispose(messageWriter); // if either an InvalidOperationException was encountered (see comment above) or the message writer was null, write the error out manually. Debug.Assert(responseStream != null, "responseStream != null"); using (XmlWriter xmlWriter = XmlWriter.Create(responseStream, XmlUtil.CreateXmlWriterSettings(encoding))) { ErrorUtils.WriteXmlError(xmlWriter, error, args.UseVerboseErrors, MaxInnerErrorDepth); } } } }
/// <summary> /// Validates that we can read or write a message with the given content-type value. /// </summary> /// <param name="contentType">The content-type value in question.</param> private static void ValidateContentType(string contentType) { if (string.IsNullOrEmpty(contentType)) { return; } // Ideally ODataLib should have a public API to get the ODataFormat from the content-type header. // Unfortunately since that's not available, we will process the content-type value to determine if the format is JSON Light. string mime; ContentTypeUtil.ReadContentType(contentType, out mime); }
/// <summary>Writes multiple top-level elements, possibly none.</summary> /// <param name="expanded">Expanded results for elements.</param> /// <param name="elements">Enumerator for elements to write.</param> protected override void WriteTopLevelElements(IExpandedResult expanded, QueryResultInfo elements) { Debug.Assert( !this.RequestDescription.IsSingleResult, "!this.RequestDescription.IsSingleResult -- otherwise WriteTopLevelElement should have been called"); if (this.RequestDescription.LinkUri) { bool needPop = this.PushSegmentForRoot(); this.WriteLinkCollection(elements); this.PopSegmentName(needPop); } else { MetadataProviderEdmModel model = this.Service.Provider.GetMetadataProviderEdmModel(); OperationWrapper operation = this.RequestDescription.LastSegmentInfo.Operation; IEdmOperation edmOperation = model.GetRelatedOperation(operation); Debug.Assert(edmOperation != null, "edmOperation != null"); IEdmCollectionTypeReference collectionType = (IEdmCollectionTypeReference)edmOperation.ReturnType; bool isJsonLightResponse = ContentTypeUtil.IsResponseMediaTypeJsonLight(this.Service, /*isEntryOrFeed*/ false); this.collectionWriter = this.writer.CreateODataCollectionWriter(isJsonLightResponse ? null : collectionType.ElementType()); ODataCollectionStart collectionStart = new ODataCollectionStart { Name = this.ComputeContainerName() }; collectionStart.SetSerializationInfo(new ODataCollectionStartSerializationInfo { CollectionTypeName = collectionType.FullName() }); this.collectionWriter.WriteStart(collectionStart); while (elements.HasMoved) { object element = elements.Current; ResourceType resourceType = element == null ? this.RequestDescription.TargetResourceType : WebUtil.GetResourceType(this.Provider, element); if (resourceType == null) { throw new InvalidOperationException(Microsoft.OData.Service.Strings.Serializer_UnsupportedTopLevelType(element.GetType())); } this.collectionWriter.WriteItem(this.GetPropertyValue(XmlConstants.XmlCollectionItemElementName, resourceType, element, false /*openProperty*/).FromODataValue()); elements.MoveNext(); } this.collectionWriter.WriteEnd(); this.collectionWriter.Flush(); } }
/// <summary> /// Initializes a new instance of <see cref="ODataMessageReaderDeserializer"/>. /// </summary> /// <param name="update">true if we're reading an update operation; false if not.</param> /// <param name="dataService">Data service for which the deserializer will act.</param> /// <param name="tracker">Tracker to use for modifications.</param> /// <param name="requestDescription">The request description to use.</param> /// <param name="enableODataServerBehavior">If true, the message reader settings will use the ODataServer behavior; /// if false, the message reader settings will use the default behavior.</param> internal ODataMessageReaderDeserializer(bool update, IDataService dataService, UpdateTracker tracker, RequestDescription requestDescription, bool enableODataServerBehavior) : base(update, dataService, tracker, requestDescription) { AstoriaRequestMessage requestMessage = dataService.OperationContext.RequestMessage; // WCF DS needs to treat content type */* as ATOM payload, so check for it here and override the content type header if (ContentTypeUtil.CompareMimeType(requestMessage.ContentType, XmlConstants.MimeAny)) { requestMessage.ContentType = XmlConstants.MimeApplicationAtom; } this.messageReader = new ODataMessageReader( requestMessage, WebUtil.CreateMessageReaderSettings(dataService, enableODataServerBehavior), dataService.Provider.GetMetadataProviderEdmModel()); }
/// <summary> /// Gets the response version for an error payload. /// </summary> /// <param name="acceptableContentTypes">A comma-separated list of client-supported MIME Accept types.</param> /// <param name="requestMaxVersion">The OData-MaxVersion of the request.</param> /// <param name="maxProtocolVersion">The max protocol version as specified in the config.</param> /// <returns>The response version to be used for an error payload.</returns> /// <remarks> /// This function is specific to exceptions. For V1 and V2, we will still return /// RequestDescription.DataServiceDefaultResponseVersion. This helps avoid breaking changes. For V3, we return /// what is in the request header, provided the version is valid. /// </remarks> internal static Version GetResponseVersionForError(string acceptableContentTypes, Version requestMaxVersion, Version maxProtocolVersion) { Debug.Assert(maxProtocolVersion != null, "maxProtocolVersion != null"); Version responseVersion = DataServiceDefaultResponseVersion; Version effectiveMaxResponseVersion = GetEffectiveMaxResponseVersion(maxProtocolVersion, requestMaxVersion); if (ContentTypeUtil.IsResponseMediaTypeJsonLight(acceptableContentTypes, false /*entityTarget*/, effectiveMaxResponseVersion)) { Debug.Assert(effectiveMaxResponseVersion >= Version4Dot0, "effectiveMaxResponseVersion should be at least Version3Dot0 to match JSON Light."); responseVersion = Version4Dot0; } return(responseVersion); }
/// <summary> /// Gets the encoding for error serialization based on the accept charset header. /// </summary> /// <param name="requestAcceptCharsetHeader">The request accept charset header.</param> /// <returns>The encoding to use.</returns> private static Encoding GetEncodingForError(string requestAcceptCharsetHeader) { Encoding encoding = null; if (requestAcceptCharsetHeader != null) { try { encoding = ContentTypeUtil.EncodingFromAcceptCharset(requestAcceptCharsetHeader); } catch (DataServiceException) { // Ignore formatting erros in Accept-Charset and rely on text. } } return(encoding ?? ContentTypeUtil.FallbackEncoding); }
/// <summary> /// Gets the content type for error serialization based on the accept header and version. /// </summary> /// <param name="requestAcceptHeader">The accept header value.</param> /// <param name="responseVersion">The response version.</param> /// <returns>The content type to use for the error response.</returns> private static string GetErrorResponseContentType(string requestAcceptHeader, Version responseVersion) { string contentType = null; if (requestAcceptHeader != null) { try { contentType = ContentTypeUtil.SelectResponseMediaType(requestAcceptHeader, false /*entityTarget*/, responseVersion); } catch (DataServiceException) { // Ignore formatting errors in Accept and rely on text. } } // TODO: change fallback type to JSON return(contentType ?? XmlConstants.MimeApplicationXml); }
/// <summary> /// Validates that we can read or write a message with the given content-type value. /// </summary> /// <param name="contentType">The content-type value in question.</param> /// <param name="isParameterPayload">true if the writer is intended to for a parameter payload, false otherwise.</param> private void ValidateContentType(string contentType, bool isParameterPayload) { if (string.IsNullOrEmpty(contentType)) { return; } // Ideally ODataLib should have a public API to get the ODataFormat from the content-type header. // Unfortunately since that's not available, we will process the content-type value to determine if the format is JSON Light. string mime; ContentTypeUtil.MediaParameter[] parameters = ContentTypeUtil.ReadContentType(contentType, out mime); if (MimeApplicationJson.Equals(mime, StringComparison.OrdinalIgnoreCase)) { // If the MDSV is < V3, application/json means JSON Verbose. Otherwise application/json;odata=verbose is JSON Verbose. if (this.context.MaxProtocolVersion < DataServiceProtocolVersion.V3 || parameters != null && parameters.Any(p => p.Name.Equals(XmlConstants.MimeODataParameterName, StringComparison.OrdinalIgnoreCase) && p.Value.Equals(MimeODataParameterVerboseValue, StringComparison.OrdinalIgnoreCase))) { // Parameter payloads do not have an Atom representation. // By default we use JSON Verbose except for UseJson() is called for JSON Light. if (!isParameterPayload) { ThrowNotSupportedExceptionForJsonVerbose(contentType); } Debug.Assert( this.context.MaxProtocolVersion >= DataServiceProtocolVersion.V3, "DataServiceVersion must be V3 or higher for parameter payloads."); } else { // If the response is in JSON Light, a service model is required to read or write it. if (this.ServiceModel == null) { ThrowInvalidOperationExceptionForJsonLightWithoutModel(); } } } }
/// <summary> /// Ensures that the media type has been parsed and that the parameters list has been populated. /// This is done lazily to avoid parsing them if GetParameterValue is never called. /// </summary> private void PopulateMediaTypeParameters() { if (this.mediaTypeParameters != null) { return; } if (this.rawMediaType != null) { string mime; Encoding encoding; this.mediaTypeParameters = ContentTypeUtil.ReadContentType(this.rawMediaType, out mime, out encoding); } // if we had no media type or it had no parameters, create an empty list if (this.mediaTypeParameters == null) { this.mediaTypeParameters = new ContentTypeUtil.MediaParameter[0]; } }
/// <summary> /// Creates an ODataWriter for writing an entry or a feed. /// </summary> /// <param name="forFeed">true when writing a feed; false when writing an entry.</param> /// <returns>The ODataWriter to use for writing the feed or entry.</returns> private DataServiceODataWriter CreateODataWriter(bool forFeed) { IEdmModel model = this.Service.Provider.GetMetadataProviderEdmModel(); Debug.Assert(model != null, "model != null"); IEdmEntitySet entitySet = null; IEdmEntityType entityType = null; if (!ContentTypeUtil.IsResponseMediaTypeJsonLight(this.Service, /*isEntryOrFeed*/ true)) { entitySet = WebUtil.GetEntitySet(this.Service.Provider, model, this.RequestDescription.TargetResourceSet); entityType = (IEdmEntityType)model.FindType(this.RequestDescription.TargetResourceType.FullName); } ODataWriter odataWriter = forFeed ? this.messageWriter.CreateODataFeedWriter(entitySet, entityType) : this.messageWriter.CreateODataEntryWriter(entitySet, entityType); return(this.Service.CreateODataWriterWrapper(odataWriter)); }
/// <summary> /// Validates that we can read or write a message with the given content-type value. /// </summary> /// <param name="contentType">The content-type value in question.</param> /// <param name="isParameterPayload">true if the writer is intended to for a parameter payload, false otherwise.</param> /// <param name="isResponse">true if content-type header value is from response, false otherwise.</param> private void ValidateContentType(string contentType, bool isParameterPayload, bool isResponse) { if (string.IsNullOrEmpty(contentType)) { return; } // Ideally ODataLib should have a public API to get the ODataFormat from the content-type header. // Unfortunately since that's not available, we will process the content-type value to determine if the format is JSON Light. string mime; ContentTypeUtil.ReadContentType(contentType, out mime); if (MimeApplicationJson.Equals(mime, StringComparison.OrdinalIgnoreCase)) { if (isResponse && this.UsingAtom) { ThrowInvalidOperationExceptionForJsonLightWithoutModel(); } } }
/// <summary> /// Gets the EDM model from the service. /// </summary> /// <returns>The EDM model or null.</returns> private IEdmModel GetModelFromService() { Debug.Assert(this.service != null, "this.service != null"); // DEVNOTE: Its unclear why this check for OperationContext being non-null is needed, // or what it has to do with the model. It was refactored from another place, and more // investigation is needed. if (this.service.OperationContext != null) { Debug.Assert(this.requestDescription != null, "this.requestDescription != null"); bool isEntryOrFeed = this.requestDescription.TargetKind == RequestTargetKind.Resource; if (!ContentTypeUtil.IsResponseMediaTypeJsonLight(this.service, isEntryOrFeed)) { Debug.Assert(this.service.Provider != null, "this.service.Provider != null"); MetadataProviderEdmModel metadataProviderEdmModel = this.service.Provider.GetMetadataProviderEdmModel(); Debug.Assert(metadataProviderEdmModel.Mode == MetadataProviderEdmModelMode.Serialization, "Model expected to be in serialization mode."); return(metadataProviderEdmModel); } } return(null); }
/// <summary>Initializes a new <see cref="ResponseBodyWriter"/> that can write the body of a response.</summary> /// <param name="service">Service for the request being processed.</param> /// <param name="queryResults">Enumerator for results.</param> /// <param name="requestDescription">Description of request made to the system.</param> /// <param name="actualResponseMessageWhoseHeadersMayBeOverridden">IODataResponseMessage instance for the response.</param> internal ResponseBodyWriter( IDataService service, QueryResultInfo queryResults, RequestDescription requestDescription, IODataResponseMessage actualResponseMessageWhoseHeadersMayBeOverridden) { Debug.Assert(service != null, "service != null"); Debug.Assert(requestDescription != null, "requestDescription != null"); Debug.Assert(actualResponseMessageWhoseHeadersMayBeOverridden != null, "actualResponseMessageWhoseHeadersMayBeOverridden != null"); this.service = service; this.queryResults = queryResults; this.requestDescription = requestDescription; this.actualResponseMessageWhoseHeadersMayBeOverridden = actualResponseMessageWhoseHeadersMayBeOverridden; Debug.Assert(this.PayloadKind != ODataPayloadKind.Unsupported, "payloadKind != ODataPayloadKind.Unsupported"); this.encoding = ContentTypeUtil.EncodingFromAcceptCharset(this.service.OperationContext.RequestMessage.GetRequestAcceptCharsetHeader()); if (this.PayloadKind == ODataPayloadKind.Entry || this.PayloadKind == ODataPayloadKind.Feed || this.PayloadKind == ODataPayloadKind.Property || this.PayloadKind == ODataPayloadKind.Collection || this.PayloadKind == ODataPayloadKind.EntityReferenceLink || this.PayloadKind == ODataPayloadKind.EntityReferenceLinks || this.PayloadKind == ODataPayloadKind.Error || this.PayloadKind == ODataPayloadKind.ServiceDocument || this.PayloadKind == ODataPayloadKind.Parameter) { AstoriaRequestMessage requestMessage = service.OperationContext.RequestMessage; IODataResponseMessage responseMessageOnOperationContext = service.OperationContext.ResponseMessage; Version effectiveMaxResponseVersion = VersionUtil.GetEffectiveMaxResponseVersion(service.Configuration.DataServiceBehavior.MaxProtocolVersion.ToVersion(), requestMessage.RequestMaxVersion); bool isEntityOrFeed = this.PayloadKind == ODataPayloadKind.Entry || this.PayloadKind == ODataPayloadKind.Feed; if (ContentTypeUtil.IsResponseMediaTypeJsonLight(requestMessage.GetAcceptableContentTypes(), isEntityOrFeed, effectiveMaxResponseVersion)) { // If JSON light 'wins', then bump the version to V3. requestDescription.VerifyAndRaiseResponseVersion(VersionUtil.Version4Dot0, service); responseMessageOnOperationContext.SetHeader(XmlConstants.HttpODataVersion, XmlConstants.ODataVersion4Dot0 + ";"); } } if (this.requestDescription.TargetKind == RequestTargetKind.MediaResource) { Debug.Assert(this.PayloadKind == ODataPayloadKind.BinaryValue, "payloadKind == ODataPayloadKind.BinaryValue"); // Note that GetReadStream will set the ResponseETag before it returns this.mediaResourceStream = service.StreamProvider.GetReadStream( this.queryResults.Current, this.requestDescription.StreamProperty, this.service.OperationContext); } else if (this.PayloadKind != ODataPayloadKind.BinaryValue) { IEdmModel model; if (this.PayloadKind == ODataPayloadKind.MetadataDocument) { model = MetadataSerializer.PrepareModelForSerialization(this.service.Provider, this.service.Configuration); } else { model = this.GetModelFromService(); } // Create the message writer using which the response needs to be written. this.messageWriterBuilder = MessageWriterBuilder.ForNormalRequest( this.service, this.requestDescription, this.actualResponseMessageWhoseHeadersMayBeOverridden, model); this.messageWriter = this.messageWriterBuilder.CreateWriter(); try { // Make sure all the headers are written before the method returns. this.contentFormat = ODataUtils.SetHeadersForPayload(this.messageWriter, this.PayloadKind); } catch (ODataContentTypeException contentTypeException) { throw new DataServiceException(415, null, Strings.DataServiceException_UnsupportedMediaType, null, contentTypeException); } Debug.Assert(requestDescription.ResponseFormat != null, "Response format should already have been determined."); Debug.Assert(ReferenceEquals(this.contentFormat, requestDescription.ResponseFormat.Format), "Response format in request description did not match format when writing."); if (this.PayloadKind == ODataPayloadKind.Value && !String.IsNullOrEmpty(this.requestDescription.MimeType)) { this.actualResponseMessageWhoseHeadersMayBeOverridden.SetHeader(XmlConstants.HttpContentType, this.requestDescription.MimeType); } // EPM is currently removed, but this doesn't seem to be used for EPM only. The old comment was saying: // In astoria, there is a bug in V1/V2 that while computing response version, we did not take // epm into account. Hence while creating the writer, we need to pass the RequestDescription.ActualResponseVersion // so that ODataLib can do the correct payload validation. But we need to write the response version without // the epm into the response headers because of backward-compat issue. Hence over-writing the response version // header with the wrong version value. string responseVersion = this.requestDescription.ResponseVersion.ToString() + ";"; this.actualResponseMessageWhoseHeadersMayBeOverridden.SetHeader(XmlConstants.HttpODataVersion, responseVersion); } }
public void UnqualifiedJsonShouldBeConsideredJsonLightIfMaxVersionIsGreaterThan3() { ContentTypeUtil.IsResponseMediaTypeJsonLight("application/json", true, new Version(5, 0)).Should().BeTrue(); }
public void HttpProcessUtilityReadMediaTypeTest() { MediaTypeTest[] tests = new MediaTypeTest[] { new MediaTypeTest() { InputText = "", ExceptionExpected = true }, new MediaTypeTest() { InputText = "text", ExceptionExpected = true }, new MediaTypeTest() { InputText = "text/", ExceptionExpected = true }, new MediaTypeTest() { InputText = "text /plain", ExceptionExpected = true }, new MediaTypeTest() { InputText = "text/ plain", ExceptionExpected = true }, new MediaTypeTest() { InputText = "text/plain a=b", ExceptionExpected = true }, new MediaTypeTest() { InputText = "text/plain;a", ExceptionExpected = true }, new MediaTypeTest() { InputText = "text/plain;a =b", ExceptionExpected = true }, new MediaTypeTest() { InputText = "text/plain;a= b", ExceptionExpected = true }, new MediaTypeTest() { InputText = "text/plain;;", ExceptionExpected = true }, new MediaTypeTest() { InputText = "text/plain;\"a\"=b;", ExceptionExpected = true }, new MediaTypeTest() { InputText = "text/plain;a=b=c; cc=dd ", ExceptionExpected = true }, }; foreach (var test in tests) { string mimeType = null; Action testAction = () => ContentTypeUtil.ReadContentType(test.InputText, out mimeType); testAction.ShouldThrow <Exception>(); } }
public void JsonWithNoMetadataShouldBeConsideredJson() { ContentTypeUtil.IsNotJson("application/json").Should().BeFalse(); }
/// <summary> /// process the batch response /// </summary> /// <returns>an instance of the DataServiceResponse, containing individual operation responses for this batch request.</returns> private DataServiceResponse HandleBatchResponse() { bool batchMessageReaderOwned = true; try { if ((this.batchResponseMessage == null) || (this.batchResponseMessage.StatusCode == (int)HttpStatusCode.NoContent)) { // we always expect a response to our batch POST request throw Error.InvalidOperation(Strings.Batch_ExpectedResponse(1)); } Func <Stream> getResponseStream = () => this.ResponseStream; // We are not going to use the responseVersion returned from this call, as the $batch request itself doesn't apply versioning // of the responses on the root level. The responses are versioned on the part level. (Note that the version on the $batch level // is actually used to version the batch itself, but we for now we only recognize a single version so to keep it backward compatible // we don't check this here. Also note that the HandleResponse method will verify that we can support the version, that is it's // lower than the highest version we understand). Version responseVersion; BaseSaveResult.HandleResponse( this.RequestInfo, (HttpStatusCode)this.batchResponseMessage.StatusCode, // statusCode this.batchResponseMessage.GetHeader(XmlConstants.HttpODataVersion), // responseVersion getResponseStream, // getResponseStream true, // throwOnFailure out responseVersion); if (this.ResponseStream == null) { Error.ThrowBatchExpectedResponse(InternalError.NullResponseStream); } // Create the message and the message reader. this.batchResponseMessage = new HttpWebResponseMessage(new HeaderCollection(this.batchResponseMessage), this.batchResponseMessage.StatusCode, getResponseStream); ODataMessageReaderSettings messageReaderSettings = this.RequestInfo.GetDeserializationInfo(/*mergeOption*/ null).ReadHelper.CreateSettings(); // No need to pass in any model to the batch reader. this.batchMessageReader = new ODataMessageReader(this.batchResponseMessage, messageReaderSettings); ODataBatchReader batchReader; try { batchReader = this.batchMessageReader.CreateODataBatchReader(); } catch (ODataContentTypeException contentTypeException) { string mime; Encoding encoding; Exception inner = contentTypeException; ContentTypeUtil.ReadContentType(this.batchResponseMessage.GetHeader(XmlConstants.HttpContentType), out mime, out encoding); if (String.Equals(XmlConstants.MimeTextPlain, mime)) { inner = GetResponseText( this.batchResponseMessage.GetStream, (HttpStatusCode)this.batchResponseMessage.StatusCode); } throw Error.InvalidOperation(Strings.Batch_ExpectedContentType(this.batchResponseMessage.GetHeader(XmlConstants.HttpContentType)), inner); } DataServiceResponse response = this.HandleBatchResponseInternal(batchReader); // In case of successful processing of at least the beginning of the batch, the message reader is owned by the returned response // (or rather by the IEnumerable of operation responses inside it). // It will be disposed once the operation responses are enumerated (since the IEnumerator should be disposed once used). // In that case we must NOT dispose it here, since that enumeration can exist long after we return from this method. batchMessageReaderOwned = false; return(response); } catch (DataServiceRequestException) { throw; } catch (InvalidOperationException ex) { HeaderCollection headers = new HeaderCollection(this.batchResponseMessage); int statusCode = this.batchResponseMessage == null ? (int)HttpStatusCode.InternalServerError : (int)this.batchResponseMessage.StatusCode; DataServiceResponse response = new DataServiceResponse(headers, statusCode, new OperationResponse[0], this.IsBatchRequest); throw new DataServiceRequestException(Strings.DataServiceException_GeneralError, ex, response); } finally { if (batchMessageReaderOwned) { Util.Dispose(ref this.batchMessageReader); } } }
public void AtomShouldNotBeConsideredJsonLight() { ContentTypeUtil.IsResponseMediaTypeJsonLight("application/atom+xml", true, V4).Should().BeFalse(); }
public void CheckForBeingJsonShouldBeCaseInsensitive() { ContentTypeUtil.IsNotJson("aPPLiCATioN/jSOn").Should().BeFalse(); ContentTypeUtil.IsNotJson("aPPLiCATioN/ATom+XMl").Should().BeTrue(); }
public void CheckForBeingJsonLightShouldBeCaseInsensitive() { ContentTypeUtil.IsResponseMediaTypeJsonLight("AppLICation/JSoN;ODatA.MeTaDAtA=FulL", true, V4).Should().BeTrue(); }
public static string GetResourceUrl(string assemblyName, string resourceName, bool shorturl) { string extension = StringUtil.GetExtension(resourceName); string url = null; string version = string.Empty; IArea site = AreaConfig.Instance; switch (extension) { case ".css": url = Web.Utility.FormatCssUrl(site, "_res.aspx?r="); version = AreaConfig.Instance.CssVersion; break; case ".js": url = Web.Utility.FormatJsUrl(site, "_res.aspx?r="); version = AreaConfig.Instance.JsVersion; break; default: url = StringUtil.CombinUrl(site.VirtualPath, "_res.aspx?r="); break; } if (shorturl) { assemblyName = assemblyName.Substring(PRENAMESPACE.Length); resourceName = resourceName.Substring(PRENAMESPACE.Length).Substring(assemblyName.Length + 1); } url += Convert.ToBase64String(Encoding.ASCII.GetBytes(resourceName)) + "&t=" + Convert.ToBase64String(Encoding.ASCII.GetBytes(assemblyName)); string contentType = ContentTypeUtil.GetContentType(extension); // short the url if (contentType == "text/css") { contentType = "0"; } else if (contentType == "text/javascript") { contentType = "1"; } url += ("&z=" + contentType); if (StringUtil.HasText(version) && site != null && !site.CombineCss) { url += ("&v=" + version); } if (shorturl) { url += "&su=1"; } return(url); }
public void AtomShouldNotBeConsideredJson() { ContentTypeUtil.IsNotJson("application/atom+xml").Should().BeTrue(); }
public void NullContentTypeShouldNotBeConsideredJson() { ContentTypeUtil.IsNotJson(null).Should().BeTrue(); }
public void JsonWithNoMetadataShouldBeConsideredJsonLight() { ContentTypeUtil.IsResponseMediaTypeJsonLight("application/json;odata.metadata=none", true, V4).Should().BeTrue(); }
public void UnqualifiedJsonShouldBeConsideredJsonLightForNonEntityPayloadsIfMaxVersionIs3() { ContentTypeUtil.IsResponseMediaTypeJsonLight("application/json", false, V4).Should().BeTrue(); }
private MaterializeAtom ReadPropertyFromRawData(ClientPropertyAnnotation property) { DataServiceContext context = (DataServiceContext)this.Source; bool merging = context.ApplyingChanges; try { context.ApplyingChanges = true; // if this is the data property for a media entry, what comes back // is the raw value (no markup) #if ASTORIA_OPEN_OBJECT object openProps = null; #endif string mimeType = null; Encoding encoding = null; Type elementType = property.EntityCollectionItemType ?? property.NullablePropertyType; IList results = (IList)Activator.CreateInstance(typeof(List <>).MakeGenericType(elementType)); ContentTypeUtil.ReadContentType(this.ContentType, out mimeType, out encoding); using (Stream responseStream = this.GetResponseStream()) { // special case byte[], and for everything else let std conversion kick-in if (property.PropertyType == typeof(byte[])) { int total = checked ((int)this.ContentLength); byte[] buffer = null; if (total >= 0) { buffer = LoadPropertyResult.ReadByteArrayWithContentLength(responseStream, total); } else { buffer = LoadPropertyResult.ReadByteArrayChunked(responseStream); } results.Add(buffer); #if ASTORIA_OPEN_OBJECT property.SetValue(this.entity, buffer, this.propertyName, ref openProps, false); #else property.SetValue(this.entity, buffer, this.propertyName, false); #endif } else { // responseStream will disposed, StreamReader doesn't need to dispose of it. StreamReader reader = new StreamReader(responseStream, encoding); object convertedValue = property.PropertyType == typeof(string) ? reader.ReadToEnd() : ClientConvert.ChangeType(reader.ReadToEnd(), property.PropertyType); results.Add(convertedValue); #if ASTORIA_OPEN_OBJECT property.SetValue(this.entity, convertedValue, this.propertyName, ref openProps, false); #else property.SetValue(this.entity, convertedValue, this.propertyName, false); #endif } } #if ASTORIA_OPEN_OBJECT Debug.Assert(openProps == null, "These should not be set in this path"); #endif if (property.MimeTypeProperty != null) { // an implication of this 3rd-arg-null is that mime type properties cannot be open props #if ASTORIA_OPEN_OBJECT property.MimeTypeProperty.SetValue(this.entity, mimeType, null, ref openProps, false); Debug.Assert(openProps == null, "These should not be set in this path"); #else property.MimeTypeProperty.SetValue(this.entity, mimeType, null, false); #endif } return(MaterializeAtom.CreateWrapper(context, results)); } finally { context.ApplyingChanges = merging; } }
public void ValidHttpProcessUtilityReadMediaTypeTest() { MediaTypeTest[] tests = new MediaTypeTest[] { new MediaTypeTest() { InputText = "text/plain", MediaType = "text/plain" }, new MediaTypeTest() { InputText = "text/plain ", MediaType = "text/plain", OutputText = "text/plain" }, new MediaTypeTest() { InputText = "text/plain;", MediaType = "text/plain", OutputText = "text/plain" }, new MediaTypeTest() { InputText = "text/plain;a=b", MediaType = "text/plain", OutputText = "text/plain;a=b", Parameters = new ContentTypeUtil.MediaParameter[] { new ContentTypeUtil.MediaParameter("a", "b", false) } }, new MediaTypeTest() { InputText = "text/plain;a=b; cc=dd ", MediaType = "text/plain", OutputText = "text/plain;a=b;cc=dd", Parameters = new ContentTypeUtil.MediaParameter[] { new ContentTypeUtil.MediaParameter("a", "b", false), new ContentTypeUtil.MediaParameter("cc", "dd", false) } }, new MediaTypeTest() { InputText = "text/plain;a=", MediaType = "text/plain", Parameters = new ContentTypeUtil.MediaParameter[] { new ContentTypeUtil.MediaParameter("a", "", false) } }, new MediaTypeTest() { InputText = "text/plain;a=\"b\"", MediaType = "text/plain", Parameters = new ContentTypeUtil.MediaParameter[] { new ContentTypeUtil.MediaParameter("a", "b", true) } }, new MediaTypeTest() // its weird that we did not fail when no name was specified { InputText = "text/plain;=b", MediaType = "text/plain", Parameters = new ContentTypeUtil.MediaParameter[] { new ContentTypeUtil.MediaParameter("", "b", false) } }, new MediaTypeTest() { InputText = "text/plain;a=", MediaType = "text/plain", Parameters = new ContentTypeUtil.MediaParameter[] { new ContentTypeUtil.MediaParameter("a", "", false) } }, }; foreach (var test in tests) { string mimeType = null; ContentTypeUtil.MediaParameter[] parameters = null; parameters = ContentTypeUtil.ReadContentType(test.InputText, out mimeType); Assert.AreEqual(mimeType, test.MediaType, "Media types match."); Assert.AreEqual(parameters != null, test.Parameters != null, "Parameter nullability matches."); if (parameters != null) { for (int i = 0; i < parameters.Length; i++) { Assert.AreEqual(parameters[i].Name, test.Parameters[i].Name, "Parameters name do not match"); Assert.AreEqual(parameters[i].Value, test.Parameters[i].Value, "Parameters value do not match"); Assert.AreEqual(parameters[i].GetOriginalValue(), test.Parameters[i].GetOriginalValue(), "Parameters original value do not match"); Assert.AreEqual(test.OutputText ?? test.InputText, ContentTypeUtil.WriteContentType(mimeType, parameters)); } } } }
/// <summary> /// Check to see if the resource to be inserted is a media descriptor, and if so /// setup a POST request for the media content first and turn the rest of /// the operation into a PUT to update the rest of the properties. /// </summary> /// <param name="entityDescriptor">The resource to check/process</param> /// <returns>An instance of ODataRequestMessage to do POST to the media resource</returns> private ODataRequestMessageWrapper CheckAndProcessMediaEntryPost(EntityDescriptor entityDescriptor) { // TODO: Revisit the design of how media link entries are handled during update ClientEdmModel model = this.RequestInfo.Model; ClientTypeAnnotation type = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); if (!type.IsMediaLinkEntry && !entityDescriptor.IsMediaLinkEntry) { // this is not a media link descriptor, process normally return(null); } if (type.MediaDataMember == null && entityDescriptor.SaveStream == null) { // The entity is marked as MLE but we don't have the content property // and the user didn't set the save stream. throw Error.InvalidOperation(Strings.Context_MLEWithoutSaveStream(type.ElementTypeName)); } Debug.Assert( (type.MediaDataMember != null && entityDescriptor.SaveStream == null) || (type.MediaDataMember == null && entityDescriptor.SaveStream != null), "Only one way of specifying the MR content is allowed."); ODataRequestMessageWrapper mediaRequest = null; if (type.MediaDataMember != null) { string contentType = null; int contentLength = 0; if (type.MediaDataMember.MimeTypeProperty == null) { contentType = XmlConstants.MimeApplicationOctetStream; } else { object mimeTypeValue = type.MediaDataMember.MimeTypeProperty.GetValue(entityDescriptor.Entity); String mimeType = mimeTypeValue != null?mimeTypeValue.ToString() : null; if (String.IsNullOrEmpty(mimeType)) { throw Error.InvalidOperation( Strings.Context_NoContentTypeForMediaLink( type.ElementTypeName, type.MediaDataMember.MimeTypeProperty.PropertyName)); } contentType = mimeType; } object value = type.MediaDataMember.GetValue(entityDescriptor.Entity); if (value == null) { this.mediaResourceRequestStream = null; } else { byte[] buffer = value as byte[]; if (buffer == null) { string mime; Encoding encoding; ContentTypeUtil.ReadContentType(contentType, out mime, out encoding); if (encoding == null) { encoding = Encoding.UTF8; contentType += XmlConstants.MimeTypeUtf8Encoding; } buffer = encoding.GetBytes(ClientConvert.ToString(value)); } contentLength = buffer.Length; // Need to specify that the buffer is publicly visible as we need to access it later on this.mediaResourceRequestStream = new MemoryStream(buffer, 0, buffer.Length, false, true); } HeaderCollection headers = new HeaderCollection(); headers.SetHeader(XmlConstants.HttpContentLength, contentLength.ToString(CultureInfo.InvariantCulture)); headers.SetHeader(XmlConstants.HttpContentType, contentType); mediaRequest = this.CreateMediaResourceRequest( entityDescriptor.GetResourceUri(this.RequestInfo.BaseUriResolver, false /*queryLink*/), XmlConstants.HttpMethodPost, Util.ODataVersion4, type.MediaDataMember == null, // sendChunked true, // applyResponsePreference headers, entityDescriptor); } else { HeaderCollection headers = new HeaderCollection(); this.SetupMediaResourceRequest(headers, entityDescriptor.SaveStream, null /*etag*/); mediaRequest = this.CreateMediaResourceRequest( entityDescriptor.GetResourceUri(this.RequestInfo.BaseUriResolver, false /*queryLink*/), XmlConstants.HttpMethodPost, Util.ODataVersion4, type.MediaDataMember == null, // sendChunked true, // applyResponsePreference headers, entityDescriptor); } // Convert the insert into an update for the media link descriptor we just created // (note that the identity still needs to be fixed up on the resbox once // the response comes with the 'location' header; that happens during processing // of the response in SavedResource()) entityDescriptor.State = EntityStates.Modified; return(mediaRequest); }
public void UnspecifiedJsonShouldBeConsideredJson() { ContentTypeUtil.IsNotJson("application/json").Should().BeFalse(); }