/// <summary> /// Creates an operation response message that can be used to write the operation content to. /// </summary> /// <param name="outputStream">The output stream underlying the operation 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 write the operation content.</returns> internal static ODataBatchOperationResponseMessage CreateWriteMessage( Stream outputStream, IODataBatchOperationListener operationListener, IODataUrlResolver urlResolver) { Func <Stream> streamCreatorFunc = () => ODataBatchUtils.CreateBatchOperationWriteStream(outputStream, operationListener); return(new ODataBatchOperationResponseMessage(streamCreatorFunc, /*headers*/ null, operationListener, /*contentId*/ null, urlResolver, /*writing*/ true)); }
/// <summary> /// Parses the request line of a batch operation request. /// </summary> /// <param name="requestLine">The request line as a string.</param> /// <param name="httpMethod">The parsed HTTP method of the request.</param> /// <param name="requestUri">The parsed <see cref="Uri"/> of the request.</param> private void ParseRequestLine(string requestLine, out string httpMethod, out Uri requestUri) { Debug.Assert(!this.inputContext.ReadingResponse, "Must only be called for requests."); // Batch Request: POST /Customers HTTP/1.1 // Since the uri can contain spaces, the only way to read the request url, is to // check for first space character and last space character and anything between // them. int firstSpaceIndex = requestLine.IndexOf(' '); // Check whether there are enough characters after the first space for the 2nd and 3rd segments // (and a whitespace in between) if (firstSpaceIndex <= 0 || requestLine.Length - 3 <= firstSpaceIndex) { // only 1 segment or empty first segment or not enough left for 2nd and 3rd segments throw new ODataException(Strings.ODataBatchReaderStream_InvalidRequestLine(requestLine)); } int lastSpaceIndex = requestLine.LastIndexOf(' '); if (lastSpaceIndex < 0 || lastSpaceIndex - firstSpaceIndex - 1 <= 0 || requestLine.Length - 1 <= lastSpaceIndex) { // only 2 segments or empty 2nd or 3rd segments // only 1 segment or empty first segment or not enough left for 2nd and 3rd segments throw new ODataException(Strings.ODataBatchReaderStream_InvalidRequestLine(requestLine)); } httpMethod = requestLine.Substring(0, firstSpaceIndex); // Request - Http method string uriSegment = requestLine.Substring(firstSpaceIndex + 1, lastSpaceIndex - firstSpaceIndex - 1); // Request - Request uri string httpVersionSegment = requestLine.Substring(lastSpaceIndex + 1); // Request - Http version // Validate HttpVersion if (string.CompareOrdinal(ODataConstants.HttpVersionInBatching, httpVersionSegment) != 0) { throw new ODataException(Strings.ODataBatchReaderStream_InvalidHttpVersionSpecified(httpVersionSegment, ODataConstants.HttpVersionInBatching)); } // NOTE: this method will throw if the method is not recognized. HttpUtils.ValidateHttpMethod(httpMethod); // Validate the HTTP method when reading a request if (this.batchStream.ChangeSetBoundary != null) { // allow all methods except for GET if (HttpUtils.IsQueryMethod(httpMethod)) { throw new ODataException(Strings.ODataBatch_InvalidHttpMethodForChangeSetRequest(httpMethod)); } } requestUri = new Uri(uriSegment, UriKind.RelativeOrAbsolute); requestUri = ODataBatchUtils.CreateOperationRequestUri(requestUri, this.inputContext.MessageReaderSettings.BaseUri, this.urlResolver); }
/// <summary> /// Creates an operation request message that can be used to write the operation content to. /// </summary> /// <param name="outputStream">The output stream underlying the operation 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="operationListener">The operation listener.</param> /// <param name="urlResolver">The (optional) URL resolver for the message to create.</param> /// <returns>An <see cref="ODataBatchOperationRequestMessage"/> to write the request content to.</returns> internal static ODataBatchOperationRequestMessage CreateWriteMessage( Stream outputStream, string method, Uri requestUrl, IODataBatchOperationListener operationListener, IODataUrlResolver urlResolver) { Debug.Assert(outputStream != null, "outputStream != null"); Debug.Assert(operationListener != null, "operationListener != null"); Func <Stream> streamCreatorFunc = () => ODataBatchUtils.CreateBatchOperationWriteStream(outputStream, operationListener); return(new ODataBatchOperationRequestMessage(streamCreatorFunc, method, requestUrl, /*headers*/ null, operationListener, /*contentId*/ null, urlResolver, /*writing*/ true)); }
/// <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="contentId">The content-ID for the operation request message.</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, string contentId, IODataUrlResolver urlResolver) { 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, contentId, urlResolver, /*writing*/ false)); }
/// <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> /// Reads a line (all bytes until a line feed) from the underlying stream. /// </summary> /// <returns>Returns the string that was read from the underyling stream (not including a terminating line feed), or null if the end of input was reached.</returns> private string ReadLine() { Debug.Assert(this.batchEncoding != null, "Batch encoding should have been established on first call to SkipToBoundary."); Debug.Assert(this.lineBuffer != null && this.lineBuffer.Length == LineBufferLength, "Line buffer should have been created."); // The number of bytes in the line buffer that make up the line. int lineBufferSize = 0; // Start with the pre-allocated line buffer array. byte[] bytesForString = this.lineBuffer; ODataBatchReaderStreamScanResult scanResult = ODataBatchReaderStreamScanResult.NoMatch; while (scanResult != ODataBatchReaderStreamScanResult.Match) { int byteCount, lineEndStartPosition, lineEndEndPosition; scanResult = this.batchBuffer.ScanForLineEnd(out lineEndStartPosition, out lineEndEndPosition); switch (scanResult) { case ODataBatchReaderStreamScanResult.NoMatch: // Copy all the bytes in the batchBuffer into the result byte[] and then continue byteCount = this.batchBuffer.NumberOfBytesInBuffer; if (byteCount > 0) { // TODO: [Design] Consider security limits for data being read ODataBatchUtils.EnsureArraySize(ref bytesForString, lineBufferSize, byteCount); Buffer.BlockCopy(this.batchBuffer.Bytes, this.batchBuffer.CurrentReadPosition, bytesForString, lineBufferSize, byteCount); lineBufferSize += byteCount; } if (this.underlyingStreamExhausted) { if (lineBufferSize == 0) { // If there's nothing more to pull from the underlying stream, and we didn't read anything // in this invocation of ReadLine(), return null to indicate end of input. return(null); } // Nothing more to read; stop looping scanResult = ODataBatchReaderStreamScanResult.Match; this.batchBuffer.SkipTo(this.batchBuffer.CurrentReadPosition + byteCount); } else { this.underlyingStreamExhausted = this.batchBuffer.RefillFrom(this.inputContext.Stream, /*preserveFrom*/ ODataBatchReaderStreamBuffer.BufferLength); } break; case ODataBatchReaderStreamScanResult.PartialMatch: // We found the start of a line end in the buffer but could not verify whether we saw all of it. // This can happen if a line end is represented as \r\n and we found \r at the very last position in the buffer. // In this case we copy the bytes into the result byte[] and continue at the start of the line end; this will guarantee // that the next scan will find the full line end, not find any additional bytes and then skip the full line end. // It is safe to copy the string right here because we will also accept \r as a line end; we are just not sure whether there // will be a subsequent \n. // This can also happen if the last byte in the stream is \r. byteCount = lineEndStartPosition - this.batchBuffer.CurrentReadPosition; if (byteCount > 0) { ODataBatchUtils.EnsureArraySize(ref bytesForString, lineBufferSize, byteCount); Buffer.BlockCopy(this.batchBuffer.Bytes, this.batchBuffer.CurrentReadPosition, bytesForString, lineBufferSize, byteCount); lineBufferSize += byteCount; } if (this.underlyingStreamExhausted) { // Nothing more to read; stop looping scanResult = ODataBatchReaderStreamScanResult.Match; this.batchBuffer.SkipTo(lineEndStartPosition + 1); } else { this.underlyingStreamExhausted = this.batchBuffer.RefillFrom(this.inputContext.Stream, /*preserveFrom*/ lineEndStartPosition); } break; case ODataBatchReaderStreamScanResult.Match: // We found a line end in the buffer Debug.Assert(lineEndStartPosition >= this.batchBuffer.CurrentReadPosition, "Line end must be at or after current position."); Debug.Assert(lineEndEndPosition < this.batchBuffer.CurrentReadPosition + this.batchBuffer.NumberOfBytesInBuffer, "Line end must finish withing buffer range."); byteCount = lineEndStartPosition - this.batchBuffer.CurrentReadPosition; if (byteCount > 0) { ODataBatchUtils.EnsureArraySize(ref bytesForString, lineBufferSize, byteCount); Buffer.BlockCopy(this.batchBuffer.Bytes, this.batchBuffer.CurrentReadPosition, bytesForString, lineBufferSize, byteCount); lineBufferSize += byteCount; } this.batchBuffer.SkipTo(lineEndEndPosition + 1); break; default: throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReaderStream_ReadLine)); } } Debug.Assert(bytesForString != null, "bytesForString != null"); return(this.CurrentEncoding.GetString(bytesForString, 0, lineBufferSize)); }