/// <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;
 }
Beispiel #3
0
        /// <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);
        }
Beispiel #4
0
        /// <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);
        }
Beispiel #6
0
 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);
 }
Beispiel #7
0
        /// <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;
        }
Beispiel #9
0
        /// <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);
        }
Beispiel #11
0
        /// <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;
     }
 }
Beispiel #13
0
        /// <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);
        }
Beispiel #14
0
        /// <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;
        }
Beispiel #16
0
        /// <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);
        }
Beispiel #17
0
        /// <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);
 }
Beispiel #19
0
        /// <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;
        }
Beispiel #23
0
        /// <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);
        }
Beispiel #25
0
 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;
 }
Beispiel #26
0
 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;
        }