/// <summary> /// Reads the headers of a part. /// </summary> /// <param name="contentId">Content-ID read from changeset header, null if changeset part detected</param> /// <returns>true if the start of a changeset part was detected; otherwise false.</returns> internal bool ProcessPartHeader(out string contentId) { Debug.Assert(this.batchEncoding != null, "Batch encoding should have been established on first call to SkipToBoundary."); bool isChangeSetPart; ODataBatchOperationHeaders headers = this.ReadPartHeaders(out isChangeSetPart); contentId = null; if (isChangeSetPart) { // determine the changeset boundary and the changeset encoding from the content type header this.DetermineChangesetBoundaryAndEncoding(headers[ODataConstants.ContentTypeHeader]); if (this.changesetEncoding == null) { // NOTE: No changeset encoding was specified in the changeset's content type header. // Determine the changeset encoding from the first bytes in the changeset. // NOTE: We do not have to skip over the potential preamble of the encoding // because the batch reader will skip over everything (incl. the preamble) // until it finds the first changeset (or batch) boundary this.changesetEncoding = this.DetectEncoding(this.multipartMixedBatchInputContext.Stream); } // Verify that we only allow single byte encodings and UTF-8 for now. ReaderValidationUtils.ValidateEncodingSupportedInBatch(this.changesetEncoding); } else if (this.ChangeSetBoundary != null) { headers.TryGetValue(ODataConstants.ContentIdHeader, out contentId); } return(isChangeSetPart); }
internal ODataBatchOperationMessage(Func<Stream> contentStreamCreatorFunc, ODataBatchOperationHeaders headers, IODataBatchOperationListener operationListener, IODataUrlResolver urlResolver, bool writing) : base(writing, false, -1L) { this.contentStreamCreatorFunc = contentStreamCreatorFunc; this.operationListener = operationListener; this.headers = headers; this.urlResolver = urlResolver; }
/// <summary> /// Reads the headers of a batch part or an operation. /// </summary> /// <returns>A dictionary of header names to header values; never null.</returns> internal ODataBatchOperationHeaders ReadHeaders() { Debug.Assert(this.batchEncoding != null, "Batch encoding should have been established on first call to SkipToBoundary."); // [Astoria-ODataLib-Integration] WCF DS Batch reader compares header names in batch part using case insensitive comparison, ODataLib is case sensitive ODataBatchOperationHeaders headers = new ODataBatchOperationHeaders(); // Read all the headers string headerLine = this.ReadLine(); while (headerLine != null && headerLine.Length > 0) { string headerName, headerValue; ValidateHeaderLine(headerLine, out headerName, out headerValue); if (headers.ContainsKeyOrdinal(headerName)) { throw new ODataException(Strings.ODataBatchReaderStream_DuplicateHeaderFound(headerName)); } headers.Add(headerName, headerValue); headerLine = this.ReadLine(); } return(headers); }
/// <summary> /// Returns the cached <see cref="ODataBatchOperationRequestMessage"/> for reading the content of an operation /// in a batch request. /// </summary> /// <returns>The message that can be used to read the content of the batch request operation from.</returns> protected override ODataBatchOperationResponseMessage CreateOperationResponseMessageImplementation() { string responseLine = this.batchStream.ReadFirstNonEmptyLine(); int statusCode = this.ParseResponseLine(responseLine); // Read all headers and create the response message ODataBatchOperationHeaders headers = this.batchStream.ReadHeaders(); if (this.currentContentId == null) { headers.TryGetValue(ODataConstants.ContentIdHeader, out this.currentContentId); } // In responses we don't need to use our batch URL resolver, since there are no cross referencing URLs // so use the URL resolver from the batch message instead. // We don't have correlation of changeset boundary between request and response messages in // changesets, so use null value for groupId. ODataBatchOperationResponseMessage responseMessage = BuildOperationResponseMessage( () => ODataBatchUtils.CreateBatchOperationReadStream(this.batchStream, headers, this), statusCode, headers, this.currentContentId, /*groupId*/ null); //// NOTE: Content-IDs for cross referencing are only supported in request messages; in responses //// we allow a Content-ID header but don't process it (i.e., don't add the content ID to the URL resolver). this.currentContentId = null; return(responseMessage); }
/// <summary> /// Constructor. /// </summary> /// <param name="contentStreamCreatorFunc">A function to retrieve the content stream for this batch operation message.</param> /// <param name="headers">The headers of the batch operation message.</param> /// <param name="operationListener">Listener interface to be notified of part changes.</param> /// <param name="urlResolver">The optional URL resolver to perform custom URL resolution for URLs written to the payload.</param> /// <param name="writing">true if the request message is being written; false when it is read.</param> private ODataBatchOperationResponseMessage( Func<Stream> contentStreamCreatorFunc, ODataBatchOperationHeaders headers, IODataBatchOperationListener operationListener, IODataUrlResolver urlResolver, bool writing) { Debug.Assert(contentStreamCreatorFunc != null, "contentStreamCreatorFunc != null"); Debug.Assert(operationListener != null, "operationListener != null"); this.message = new ODataBatchOperationMessage(contentStreamCreatorFunc, headers, operationListener, urlResolver, writing); }
internal static ODataBatchOperationReadStream CreateBatchOperationReadStream(ODataBatchReaderStream batchReaderStream, ODataBatchOperationHeaders headers, IODataBatchOperationListener operationListener) { string str; if (!headers.TryGetValue("Content-Length", out str)) { return ODataBatchOperationReadStream.Create(batchReaderStream, operationListener); } int length = int.Parse(str, CultureInfo.InvariantCulture); if (length < 0) { throw new ODataException(Strings.ODataBatchReaderStream_InvalidContentLengthSpecified(str)); } return ODataBatchOperationReadStream.Create(batchReaderStream, operationListener, length); }
/// <summary> /// Returns the cached <see cref="ODataBatchOperationRequestMessage"/> for reading the content of an operation /// in a batch request. /// </summary> /// <returns>The message that can be used to read the content of the batch request operation from.</returns> protected override ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation() { string requestLine = this.batchStream.ReadFirstNonEmptyLine(); string httpMethod; Uri requestUri; this.ParseRequestLine(requestLine, out httpMethod, out requestUri); // Read all headers and create the request message ODataBatchOperationHeaders headers = this.batchStream.ReadHeaders(); if (this.batchStream.ChangeSetBoundary != null) { if (this.currentContentId == null) { headers.TryGetValue(ODataConstants.ContentIdHeader, out this.currentContentId); if (this.currentContentId == null) { throw new ODataException(Strings.ODataBatchOperationHeaderDictionary_KeyNotFound(ODataConstants.ContentIdHeader)); } } } else if (this.InputContext.MessageReaderSettings.Version <= ODataVersion.V4) { // For backward compatibility, don't enforce uniqueness of Content-ID // in MultipartMixed outside of a changeset this.PayloadUriConverter.Reset(); } ODataBatchOperationRequestMessage requestMessage = BuildOperationRequestMessage( () => ODataBatchUtils.CreateBatchOperationReadStream(this.batchStream, headers, this), httpMethod, requestUri, headers, this.currentContentId, this.batchStream.ChangeSetBoundary, this.dependsOnIdsTracker.GetDependsOnIds(), /*dependsOnIdsValidationRequired*/ false); if (this.currentContentId != null) { this.dependsOnIdsTracker.AddDependsOnId(this.currentContentId); } this.currentContentId = null; return(requestMessage); }
/// <summary> /// Constructor. Base class constructor to create a message for an operation of a batch request/response. /// </summary> /// <param name="contentStreamCreatorFunc">A function to retrieve the content stream for this batch operation message.</param> /// <param name="headers">The headers of the batch operation message.</param> /// <param name="operationListener">Listener interface to be notified of part changes.</param> /// <param name="urlResolver">The URL resolver to perform custom URL resolution for URLs read or written from/to the payload.</param> /// <param name="writing">true if the request message is being written; false when it is read.</param> internal ODataBatchOperationMessage( Func<Stream> contentStreamCreatorFunc, ODataBatchOperationHeaders headers, IODataBatchOperationListener operationListener, IODataUrlResolver urlResolver, bool writing) : base(writing, /*disableMessageStreamDisposal*/ false, /*maxMessageSize*/ -1) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(contentStreamCreatorFunc != null, "contentStreamCreatorFunc != null"); Debug.Assert(operationListener != null, "operationListener != null"); this.contentStreamCreatorFunc = contentStreamCreatorFunc; this.operationListener = operationListener; this.headers = headers; this.urlResolver = urlResolver; }
/// <summary> /// Validates the headers that have been read for a part. /// </summary> /// <param name="headers">The set of headers to validate.</param> /// <param name="isChangeSetPart">true if the headers indicate a changeset part; otherwise false.</param> /// <returns>The set of validated headers.</returns> /// <remarks> /// An operation part is required to have content type 'application/http' and content transfer /// encoding 'binary'. A changeset is required to have content type 'multipart/mixed'. /// Note that we allow additional headers for batch parts; clients of the library can choose /// to be more strict. /// </remarks> private ODataBatchOperationHeaders ValidatePartHeaders(ODataBatchOperationHeaders headers, out bool isChangeSetPart) { string contentType; if (!headers.TryGetValue(ODataConstants.ContentTypeHeader, out contentType)) { throw new ODataException(Strings.ODataBatchReaderStream_MissingContentTypeHeader); } if (MediaTypeUtils.MediaTypeAndSubtypeAreEqual(contentType, MimeConstants.MimeApplicationHttp)) { isChangeSetPart = false; // An operation part is required to have application/http content type and // binary content transfer encoding. string transferEncoding; if (!headers.TryGetValue(ODataConstants.ContentTransferEncoding, out transferEncoding) || string.Compare(transferEncoding, ODataConstants.BatchContentTransferEncoding, StringComparison.OrdinalIgnoreCase) != 0) { throw new ODataException(Strings.ODataBatchReaderStream_MissingOrInvalidContentEncodingHeader( ODataConstants.ContentTransferEncoding, ODataConstants.BatchContentTransferEncoding)); } } else if (MediaTypeUtils.MediaTypeStartsWithTypeAndSubtype(contentType, MimeConstants.MimeMultipartMixed)) { isChangeSetPart = true; if (this.changesetBoundary != null) { // Nested changesets are not supported throw new ODataException(Strings.ODataBatchReaderStream_NestedChangesetsAreNotSupported); } } else { throw new ODataException(Strings.ODataBatchReaderStream_InvalidContentTypeSpecified( ODataConstants.ContentTypeHeader, contentType, MimeConstants.MimeMultipartMixed, MimeConstants.MimeApplicationHttp)); } return(headers); }
/// <summary> /// Constructor. Creates a request message for an operation of a batch request. /// </summary> /// <param name="contentStreamCreatorFunc">A function to create the content stream.</param> /// <param name="method">The HTTP method used for this request message.</param> /// <param name="requestUrl">The request Url for this request message.</param> /// <param name="headers">The headers for the this request message.</param> /// <param name="operationListener">Listener interface to be notified of operation changes.</param> /// <param name="urlResolver">The optional URL resolver to perform custom URL resolution for URLs written to the payload.</param> /// <param name="writing">true if the request message is being written; false when it is read.</param> private ODataBatchOperationRequestMessage( Func<Stream> contentStreamCreatorFunc, string method, Uri requestUrl, ODataBatchOperationHeaders headers, IODataBatchOperationListener operationListener, IODataUrlResolver urlResolver, bool writing) { Debug.Assert(contentStreamCreatorFunc != null, "contentStreamCreatorFunc != null"); Debug.Assert(operationListener != null, "operationListener != null"); Debug.Assert(urlResolver != null, "urlResolver != null"); this.Method = method; this.Url = requestUrl; this.message = new ODataBatchOperationMessage(contentStreamCreatorFunc, headers, operationListener, urlResolver, writing); }
/// <summary> /// Returns the cached <see cref="ODataBatchOperationRequestMessage"/> for reading the content of an operation /// in a batch request. /// </summary> /// <returns>The message that can be used to read the content of the batch request operation from.</returns> protected override ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation() { string requestLine = this.batchStream.ReadFirstNonEmptyLine(); string httpMethod; Uri requestUri; this.ParseRequestLine(requestLine, out httpMethod, out requestUri); // Read all headers and create the request message ODataBatchOperationHeaders headers = this.batchStream.ReadHeaders(); if (this.batchStream.ChangeSetBoundary != null) { if (this.currentContentId == null) { headers.TryGetValue(ODataConstants.ContentIdHeader, out this.currentContentId); if (this.currentContentId == null) { throw new ODataException(Strings.ODataBatchOperationHeaderDictionary_KeyNotFound(ODataConstants.ContentIdHeader)); } } } ODataBatchOperationRequestMessage requestMessage = BuildOperationRequestMessage( () => ODataBatchUtils.CreateBatchOperationReadStream(this.batchStream, headers, this), httpMethod, requestUri, headers, this.currentContentId, this.batchStream.ChangeSetBoundary, this.dependsOnIdsTracker.GetDependsOnIds(), /*dependsOnIdsValidationRequired*/ false); if (this.currentContentId != null) { this.dependsOnIdsTracker.AddDependsOnId(this.currentContentId); } this.currentContentId = null; return(requestMessage); }
public override void SetHeader(string headerName, string headerValue) { this.VerifyNotCompleted(); base.VerifyCanSetHeader(); if (headerValue == null) { if (this.headers != null) { this.headers.Remove(headerName); } } else { if (this.headers == null) { this.headers = new ODataBatchOperationHeaders(); } this.headers[headerName] = headerValue; } }
/// <summary> /// Returns the cached <see cref="ODataBatchOperationResponseMessage"/> for reading the content of a /// batch response. /// </summary> /// <returns>The message that can be used to read the content of the batch response from.</returns> protected override ODataBatchOperationResponseMessage CreateOperationResponseMessageImplementation() { Debug.Assert(this.mode == ReaderMode.Responses, "this.mode == ReaderMode.Responses"); Debug.Assert(this.messagePropertiesCache != null, "this.responsePropertiesCache != null"); // body. Use empty stream when request body is not present. Stream bodyContentStream = (Stream)this.messagePropertiesCache.GetPropertyValue(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameBody) ?? new ODataJsonLightBatchBodyContentReaderStream(this); int statusCode = (int) this.messagePropertiesCache.GetPropertyValue(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameStatus); string contentId = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameId); string groupId = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); ODataBatchOperationHeaders headers = (ODataBatchOperationHeaders) this.messagePropertiesCache.GetPropertyValue(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameHeaders); // Reset the response property cache since all data in cache has been processed. // So that new instance can be created during subsequent read in operation state. this.messagePropertiesCache = null; // In responses we don't need to use our batch URL resolver, since there are no cross referencing URLs // so use the URL resolver from the batch message instead. ODataBatchOperationResponseMessage responseMessage = BuildOperationResponseMessage( () => bodyContentStream, statusCode, headers, contentId, groupId); //// NOTE: Content-IDs for cross referencing are only supported in request messages; in responses //// we allow a Content-ID header but don't process it (i.e., don't add the content ID to the URL resolver). return(responseMessage); }
/// <summary> /// Creates a batch operation stream from the specified batch stream. /// </summary> /// <param name="batchReaderStream">The batch stream to create the operation read stream for.</param> /// <param name="headers">The headers of the current part; based on the header we create different, optimized stream implementations.</param> /// <param name="operationListener">The operation listener to be passed to the newly created read stream.</param> /// <returns>A new <see cref="ODataBatchOperationReadStream"/> instance.</returns> internal static ODataBatchOperationReadStream CreateBatchOperationReadStream( ODataBatchReaderStream batchReaderStream, ODataBatchOperationHeaders headers, IODataBatchOperationListener operationListener) { Debug.Assert(batchReaderStream != null, "batchReaderStream != null"); Debug.Assert(operationListener != null, "operationListener != null"); // See whether we have a Content-Length header string contentLengthValue; if (headers.TryGetValue(ODataConstants.ContentLengthHeader, out contentLengthValue)) { int length = Int32.Parse(contentLengthValue, CultureInfo.InvariantCulture); if (length < 0) { throw new ODataException(Strings.ODataBatchReaderStream_InvalidContentLengthSpecified(contentLengthValue)); } return ODataBatchOperationReadStream.Create(batchReaderStream, operationListener, length); } return ODataBatchOperationReadStream.Create(batchReaderStream, operationListener); }
/// <summary> /// Creates an operation response message that can be used to read the operation content from. /// </summary> /// <param name="batchReaderStream">The batch stream underyling the operation response message.</param> /// <param name="statusCode">The status code to use for the operation response message.</param> /// <param name="headers">The headers to use for the operation response message.</param> /// <param name="contentId">The content-ID for the operation response message.</param> /// <param name="operationListener">The operation listener.</param> /// <param name="urlResolver">The (optional) URL resolver for the message to create.</param> /// <returns>An <see cref="ODataBatchOperationResponseMessage"/> that can be used to read the operation content.</returns> internal static ODataBatchOperationResponseMessage CreateReadMessage( ODataBatchReaderStream batchReaderStream, int statusCode, ODataBatchOperationHeaders headers, string contentId, IODataBatchOperationListener operationListener, IODataUrlResolver urlResolver) { Debug.Assert(batchReaderStream != null, "batchReaderStream != null"); Debug.Assert(operationListener != null, "operationListener != null"); Func<Stream> streamCreatorFunc = () => ODataBatchUtils.CreateBatchOperationReadStream(batchReaderStream, headers, operationListener); ODataBatchOperationResponseMessage responseMessage = new ODataBatchOperationResponseMessage(streamCreatorFunc, headers, operationListener, contentId, urlResolver, /*writing*/ false); responseMessage.statusCode = statusCode; return responseMessage; }
/// <summary> /// Returns the cached <see cref="ODataBatchOperationRequestMessage"/> for reading the content of an operation /// in a batch request. /// </summary> /// <returns>The message that can be used to read the content of the batch request operation from.</returns> protected override ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation() { Debug.Assert(this.mode == ReaderMode.Requests, "this.mode == ReaderMode.Requests"); Debug.Assert(this.messagePropertiesCache != null, "this.messagePropertiesCache != null"); // id string id = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameId); // atomicityGroup string atomicityGroupId = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); if (id != null) { this.requestIds.Add(id); } if (atomicityGroupId != null) { this.requestIds.Add(atomicityGroupId); } // dependsOn // Flatten the dependsOn list by converting every groupId into request Ids, so that the caller // can decide, at the earliest opportunity, whether the depending request can be invoked. // Note that the forward reference of dependsOn id is not allowed, so the atomicGroups should have accurate // information of atomicGroup that needs to be flattened. IList <string> dependsOnReqIds = null; List <string> dependsOn = (List <string>) this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameDependsOn); if (dependsOn != null && dependsOn.Count != 0) { ValidateDependsOnId(dependsOn, atomicityGroupId, id); dependsOnReqIds = atomicGroups.GetFlattenedMessageIds(dependsOn); } // header ODataBatchOperationHeaders headers = (ODataBatchOperationHeaders)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameHeaders); // Add the atomicityGroup request header. if (atomicityGroupId != null) { headers.Add(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup, atomicityGroupId); } // body. Use empty stream when request body is not present. Stream bodyContentStream = (Stream)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameBody) ?? new ODataJsonLightBatchBodyContentReaderStream(this); // method. Support case-insensitive value of HTTP methods. string httpMethod = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameMethod); ValidateRequiredProperty(httpMethod, ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameMethod); httpMethod = httpMethod.ToUpperInvariant(); // url string url = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameUrl); ValidateRequiredProperty(url, ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameUrl); // escape any colons in the query string portion of the url int queryOptionSeparator = url.IndexOf('?'); int firstColon = url.IndexOf(':'); if (queryOptionSeparator > 0 && firstColon > 0 && queryOptionSeparator < firstColon) { url = url.Substring(0, queryOptionSeparator) + url.Substring(queryOptionSeparator).Replace(":", "%3A"); } Uri requestUri = new Uri(url, UriKind.RelativeOrAbsolute); // Reset the request property cache since all data in cache has been processed. // So that new instance can be created during subsequent read in operation state. this.messagePropertiesCache = null; ODataBatchOperationRequestMessage requestMessage = BuildOperationRequestMessage( () => bodyContentStream, httpMethod, requestUri, headers, id, atomicityGroupId, dependsOnReqIds, /*dependsOnIdsValidationRequired*/ true); return(requestMessage); }
/// <summary> /// Wrapper method with validation to scan the Json object for known properties. /// </summary> private void ScanJsonProperties() { Debug.Assert(this.jsonReader != null, "this.jsonReaders != null"); Debug.Assert(this.jsonProperties == null, "this.jsonProperties == null"); this.jsonProperties = new Dictionary <string, object>(); string contentTypeHeader = null; ODataJsonLightBatchBodyContentReaderStream bodyContentStream = null; try { // Request object start. this.jsonReader.ReadStartObject(); while (this.jsonReader.NodeType != JsonNodeType.EndObject) { // Convert to upper case to support case-insensitive request property names string propertyName = Normalize(this.jsonReader.ReadPropertyName()); switch (propertyName) { case PropertyNameId: case PropertyNameAtomicityGroup: case PropertyNameMethod: case PropertyNameUrl: { jsonProperties.Add(propertyName, this.jsonReader.ReadStringValue()); } break; case PropertyNameStatus: { jsonProperties.Add(propertyName, this.jsonReader.ReadPrimitiveValue()); } break; case PropertyNameDependsOn: { IList <string> dependsOnIds = new List <string>(); this.jsonReader.ReadStartArray(); while (this.jsonReader.NodeType != JsonNodeType.EndArray) { dependsOnIds.Add(this.jsonReader.ReadStringValue()); } this.jsonReader.ReadEndArray(); jsonProperties.Add(propertyName, dependsOnIds); } break; case PropertyNameHeaders: { ODataBatchOperationHeaders headers = new ODataBatchOperationHeaders(); // Use empty string (non-null value) to indicate that content-type header has been processed. contentTypeHeader = ""; this.jsonReader.ReadStartObject(); while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) { contentTypeHeader = headerValue; } headers.Add(headerName, headerValue); } this.jsonReader.ReadEndObject(); jsonProperties.Add(propertyName, headers); if (!this.isStreamPopulated && bodyContentStream != null) { // Populate the stream now since the body content has been cached and we now have content-type. bodyContentStream.PopulateCachedBodyContent(contentTypeHeader); } } break; case PropertyNameBody: { bodyContentStream = CreateJsonPayloadBodyContentStream(contentTypeHeader); jsonProperties.Add(propertyName, bodyContentStream); } break; default: { throw new ODataException(string.Format( CultureInfo.InvariantCulture, "Unknown property name '{0}' for message in batch", propertyName)); } } } // Request object end. this.jsonReader.ReadEndObject(); } finally { // We don't need to use the Json reader anymore. this.jsonReader = null; } }
private ODataBatchOperationResponseMessage(Func<Stream> contentStreamCreatorFunc, ODataBatchOperationHeaders headers, IODataBatchOperationListener operationListener, IODataUrlResolver urlResolver, bool writing) { this.message = new ODataBatchOperationMessage(contentStreamCreatorFunc, headers, operationListener, urlResolver, writing); }
/// <summary> /// Reads and validates the headers of a batch part. /// </summary> /// <param name="isChangeSetPart">true if the headers indicate a changeset part; otherwise false.</param> /// <returns>A dictionary of header names to header values; never null.</returns> private ODataBatchOperationHeaders ReadPartHeaders(out bool isChangeSetPart) { ODataBatchOperationHeaders partHeaders = this.ReadHeaders(); return(this.ValidatePartHeaders(partHeaders, out isChangeSetPart)); }
internal static ODataBatchOperationResponseMessage CreateReadMessage(ODataBatchReaderStream batchReaderStream, int statusCode, ODataBatchOperationHeaders headers, IODataBatchOperationListener operationListener, IODataUrlResolver urlResolver) { return new ODataBatchOperationResponseMessage(() => ODataBatchUtils.CreateBatchOperationReadStream(batchReaderStream, headers, operationListener), headers, operationListener, urlResolver, false) { statusCode = statusCode }; }
/// <summary> /// Validates the headers that have been read for a part. /// </summary> /// <param name="headers">The set of headers to validate.</param> /// <param name="isChangeSetPart">true if the headers indicate a changset part; otherwise false.</param> /// <returns>The set of validated headers.</returns> /// <remarks> /// An operation part is required to have content type 'application/http' and content transfer /// encoding 'binary'. A changeset is required to have content type 'multipart/mixed'. /// Note that we allow additional headers for batch parts; clients of the library can choose /// to be more strict. /// </remarks> private ODataBatchOperationHeaders ValidatePartHeaders(ODataBatchOperationHeaders headers, out bool isChangeSetPart) { string contentType; if (!headers.TryGetValue(ODataConstants.ContentTypeHeader, out contentType)) { throw new ODataException(Strings.ODataBatchReaderStream_MissingContentTypeHeader); } if (MediaTypeUtils.MediaTypeAndSubtypeAreEqual(contentType, MimeConstants.MimeApplicationHttp)) { isChangeSetPart = false; // An operation part is required to have application/http content type and // binary content transfer encoding. string transferEncoding; if (!headers.TryGetValue(ODataConstants.ContentTransferEncoding, out transferEncoding) || string.Compare(transferEncoding, ODataConstants.BatchContentTransferEncoding, StringComparison.OrdinalIgnoreCase) != 0) { throw new ODataException(Strings.ODataBatchReaderStream_MissingOrInvalidContentEncodingHeader( ODataConstants.ContentTransferEncoding, ODataConstants.BatchContentTransferEncoding)); } } else if (MediaTypeUtils.MediaTypeStartsWithTypeAndSubtype(contentType, MimeConstants.MimeMultipartMixed)) { isChangeSetPart = true; if (this.changesetBoundary != null) { // Nested changesets are not supported throw new ODataException(Strings.ODataBatchReaderStream_NestedChangesetsAreNotSupported); } } else { throw new ODataException(Strings.ODataBatchReaderStream_InvalidContentTypeSpecified( ODataConstants.ContentTypeHeader, contentType, MimeConstants.MimeMultipartMixed, MimeConstants.MimeApplicationHttp)); } return headers; }
/// <summary> /// Reads the headers of a batch part or an operation. /// </summary> /// <returns>A dictionary of header names to header values; never null.</returns> internal ODataBatchOperationHeaders ReadHeaders() { Debug.Assert(this.batchEncoding != null, "Batch encoding should have been established on first call to SkipToBoundary."); // [Astoria-ODataLib-Integration] WCF DS Batch reader compares header names in batch part using case insensitive comparison, ODataLib is case sensitive ODataBatchOperationHeaders headers = new ODataBatchOperationHeaders(); // Read all the headers string headerLine = this.ReadLine(); while (headerLine != null && headerLine.Length > 0) { string headerName, headerValue; ValidateHeaderLine(headerLine, out headerName, out headerValue); if (headers.ContainsKeyOrdinal(headerName)) { throw new ODataException(Strings.ODataBatchReaderStream_DuplicateHeaderFound(headerName)); } headers.Add(headerName, headerValue); headerLine = this.ReadLine(); } return headers; }
/// <summary> /// Wrapper method with validation to asynchronously scan the JSON object for known properties. /// </summary> /// <returns>A task that represents the asynchronous read operation.</returns> private async Task ScanJsonPropertiesAsync() { Debug.Assert(this.asynchronousJsonReader != null, $"{nameof(this.asynchronousJsonReader)} != null"); Debug.Assert(this.jsonProperties == null, $"{nameof(this.jsonProperties)} == null"); this.jsonProperties = new Dictionary <string, object>(); string contentTypeHeader = null; ODataJsonLightBatchBodyContentReaderStream bodyContentStream = null; try { // Request object start. await this.asynchronousJsonReader.ReadStartObjectAsync() .ConfigureAwait(false); while (this.asynchronousJsonReader.NodeType != JsonNodeType.EndObject) { // Convert to upper case to support case-insensitive request property names string propertyName = Normalize(await this.asynchronousJsonReader.ReadPropertyNameAsync().ConfigureAwait(false)); switch (propertyName) { case PropertyNameId: case PropertyNameAtomicityGroup: case PropertyNameMethod: case PropertyNameUrl: jsonProperties.Add( propertyName, await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false)); break; case PropertyNameStatus: jsonProperties.Add( propertyName, await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); break; case PropertyNameDependsOn: List <string> dependsOnIds = new List <string>(); await this.asynchronousJsonReader.ReadStartArrayAsync() .ConfigureAwait(false); while (this.asynchronousJsonReader.NodeType != JsonNodeType.EndArray) { dependsOnIds.Add(await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false)); } await this.asynchronousJsonReader.ReadEndArrayAsync() .ConfigureAwait(false); jsonProperties.Add(propertyName, dependsOnIds); break; case PropertyNameHeaders: ODataBatchOperationHeaders headers = new ODataBatchOperationHeaders(); // Use empty string (non-null value) to indicate that content-type header has been processed. contentTypeHeader = ""; await this.asynchronousJsonReader.ReadStartObjectAsync() .ConfigureAwait(false); while (this.asynchronousJsonReader.NodeType != JsonNodeType.EndObject) { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)).ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) { contentTypeHeader = headerValue; } headers.Add(headerName, headerValue); } await this.asynchronousJsonReader.ReadEndObjectAsync() .ConfigureAwait(false); jsonProperties.Add(propertyName, headers); if (!this.isStreamPopulated && bodyContentStream != null) { // Populate the stream now since the body content has been cached and we now have content-type. await bodyContentStream.PopulateCachedBodyContentAsync(contentTypeHeader) .ConfigureAwait(false); } break; case PropertyNameBody: bodyContentStream = await CreateJsonPayloadBodyContentStreamAsync(contentTypeHeader) .ConfigureAwait(false); jsonProperties.Add(propertyName, bodyContentStream); break; default: throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch(propertyName)); } } // Request object end. await this.asynchronousJsonReader.ReadEndObjectAsync() .ConfigureAwait(false); } finally { // We don't need to use the Json reader anymore. this.asynchronousJsonReader = null; } }
/// <summary> /// Creates an operation request message that can be used to read the operation content from. /// </summary> /// <param name="batchReaderStream">The batch stream underyling the operation response message.</param> /// <param name="method">The HTTP method to use for the message to create.</param> /// <param name="requestUrl">The request URL for the message to create.</param> /// <param name="headers">The headers to use for the operation request message.</param> /// <param name="operationListener">The operation listener.</param> /// <param name="urlResolver">The (optional) URL resolver for the message to create.</param> /// <returns>An <see cref="ODataBatchOperationRequestMessage"/> to read the request content from.</returns> internal static ODataBatchOperationRequestMessage CreateReadMessage( ODataBatchReaderStream batchReaderStream, string method, Uri requestUrl, ODataBatchOperationHeaders headers, IODataBatchOperationListener operationListener, IODataUrlResolver urlResolver) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(batchReaderStream != null, "batchReaderStream != null"); Debug.Assert(operationListener != null, "operationListener != null"); Func<Stream> streamCreatorFunc = () => ODataBatchUtils.CreateBatchOperationReadStream(batchReaderStream, headers, operationListener); return new ODataBatchOperationRequestMessage(streamCreatorFunc, method, requestUrl, headers, operationListener, urlResolver, /*writing*/ false); }
internal ODataBatchOperationHeaders ReadHeaders() { ODataBatchOperationHeaders headers = new ODataBatchOperationHeaders(); for (string str = this.ReadLine(); (str != null) && (str.Length > 0); str = this.ReadLine()) { string str2; string str3; ValidateHeaderLine(str, out str2, out str3); if (headers.ContainsKeyOrdinal(str2)) { throw new ODataException(Strings.ODataBatchReaderStream_DuplicateHeaderFound(str2)); } headers.Add(str2, str3); } return headers; }
private ODataBatchOperationHeaders ValidatePartHeaders(ODataBatchOperationHeaders headers, out bool isChangeSetPart) { string str; if (!headers.TryGetValue("Content-Type", out str)) { throw new ODataException(Strings.ODataBatchReaderStream_MissingContentTypeHeader); } if (MediaTypeUtils.MediaTypeAndSubtypeAreEqual(str, "application/http")) { string str2; isChangeSetPart = false; if (!headers.TryGetValue("Content-Transfer-Encoding", out str2) || (string.Compare(str2, "binary", StringComparison.OrdinalIgnoreCase) != 0)) { throw new ODataException(Strings.ODataBatchReaderStream_MissingOrInvalidContentEncodingHeader("Content-Transfer-Encoding", "binary")); } return headers; } if (!MediaTypeUtils.MediaTypeStartsWithTypeAndSubtype(str, "multipart/mixed")) { throw new ODataException(Strings.ODataBatchReaderStream_InvalidContentTypeSpecified("Content-Type", str, "multipart/mixed", "application/http")); } isChangeSetPart = true; if (this.changesetBoundary != null) { throw new ODataException(Strings.ODataBatchReaderStream_NestedChangesetsAreNotSupported); } return headers; }
internal static ODataBatchOperationRequestMessage CreateReadMessage(ODataBatchReaderStream batchReaderStream, string method, Uri requestUrl, ODataBatchOperationHeaders headers, IODataBatchOperationListener operationListener, IODataUrlResolver urlResolver) { return new ODataBatchOperationRequestMessage(() => ODataBatchUtils.CreateBatchOperationReadStream(batchReaderStream, headers, operationListener), method, requestUrl, headers, operationListener, urlResolver, false); }
/// <summary> /// Reads the headers of a batch part or an operation. /// </summary> /// <returns>A dictionary of header names to header values; never null.</returns> internal ODataBatchOperationHeaders ReadHeaders() { DebugUtils.CheckNoExternalCallers(); Debug.Assert(this.batchEncoding != null, "Batch encoding should have been established on first call to SkipToBoundary."); ODataBatchOperationHeaders headers = new ODataBatchOperationHeaders(); // Read all the headers string headerLine = this.ReadLine(); while (headerLine != null && headerLine.Length > 0) { string headerName, headerValue; ValidateHeaderLine(headerLine, out headerName, out headerValue); if (headers.ContainsKeyOrdinal(headerName)) { throw new ODataException(Strings.ODataBatchReaderStream_DuplicateHeaderFound(headerName)); } headers.Add(headerName, headerValue); headerLine = this.ReadLine(); } return headers; }