/// <summary> /// Gets the $callback query parameter if present. Also performs some validation: /// We do not allow $callback to be present if the content-type for the response is going to be anythign but JSON or plain text. /// </summary> /// <param name="message">The request message.</param> /// <param name="format">The format we will write the reponse in.</param> /// <returns>Returns the name of the callback function $callback specifies, or null if there isn't one.</returns> internal static string HandleCallbackQueryOption(AstoriaRequestMessage message, ODataFormatWithParameters format) { var functionName = message.GetQueryStringItem(XmlConstants.HttpQueryStringCallback); if (functionName != null) { // The verb must be GET (not IsQuery(), GET specifically) if (message.HttpVerb != HttpVerbs.GET) { throw new DataServiceException(400, Strings.CallbackQueryOptionHandler_GetRequestsOnly); } // If conneg didn't get a format, that means that there was some error that we will fail at later, or // it is going end up being a 204, 304, or something else with no body. For all of these cases we are OK // with 'pretending' that $callback was not there, since the other errors are probably more important than // ours, and in the non-error cases we really should not be throwing (think 204). if (format == null) { return null; } // JSON and text/plain are the only things allowed with $callback if (format.Format != ODataFormat.Json && format.Format != ODataFormat.RawValue) { throw new DataServiceException(400, Strings.CallbackQueryOptionHandler_UnsupportedContentType(format.Format.ToString())); } } return functionName; }
public void ContentTypeIsFromHost() { const string contentType = "application/atom+xml"; var host = new DataServiceHostSimulator { RequestContentType = contentType }; var requestMessage = new AstoriaRequestMessage(host); requestMessage.ContentType.Should().Be(contentType); }
public void GetAcceptableContentTypesShouldCallDelegate() { var contentTypeSelector = new AcceptableContentTypeSelectorSimulator(); var host = new DataServiceHostSimulator(); var requestMessage = new AstoriaRequestMessage(host, contentTypeSelector); requestMessage.GetAcceptableContentTypes().Should().Be(AcceptableContentTypeSelectorSimulator.GetFormatReturnValue); }
public void AbsoluteServiceUriFromHostShouldHaveTrailingSlashAppended() { var requestMessage = new AstoriaRequestMessage(new DataServiceHostSimulator { AbsoluteServiceUri = new Uri("http://temp.org") }); requestMessage.AbsoluteServiceUri.Should().Be("http://temp.org/"); }
public void GetDollarFormatQueryItemShouldGetValueFromHostGetQueryStringItemMethod() { const string queryKey = "$format"; const string queryValue = "custom"; var host = new DataServiceHostSimulator { }; host.SetQueryStringItem(queryKey, queryValue); var requestMessage = new AstoriaRequestMessage(host); requestMessage.GetQueryStringItem(queryKey).Should().Be(queryValue); }
public void GetQueryStringItemShouldGetComponentFromHostGetQueryStringItemMethod() { const string queryKey = "queryKey"; const string queryValue = "queryValue"; var host = new DataServiceHostSimulator { AbsoluteRequestUri = new Uri("http://www.service.com/there/is/not/even/a/query-string") }; host.SetQueryStringItem(queryKey, queryValue); var requestMessage = new AstoriaRequestMessage(host); requestMessage.GetQueryStringItem(queryKey).Should().Be(queryValue); }
public void Init() { this.requestMessageWithDefaultUris = new AstoriaRequestMessage(new DataServiceHostSimulator { AbsoluteServiceUri = this.absoluteServiceUri, AbsoluteRequestUri = this.absoluteRequestUri }); this.requestMessageWithQueryStrings = new AstoriaRequestMessage( new DataServiceHostSimulator { AbsoluteServiceUri = new Uri(this.absoluteServiceUri.OriginalString + "?originalServiceQueryString"), AbsoluteRequestUri = new Uri(this.absoluteRequestUri + "?originalRequestQueryString"), }); }
public void RequestAcceptCharSetIsFromHost() { const string value = "some_other_value"; var host = new DataServiceHostSimulator { RequestAcceptCharSet = value }; var requestMessage = new AstoriaRequestMessage(host); requestMessage.GetHeader("Accept-Charset").Should().Be(value); }
public void RequestContentTypeIsFromHost() { const string value = "a-content-type"; var host = new DataServiceHostSimulator { RequestContentType = value }; var requestMessage = new AstoriaRequestMessage(host); requestMessage.GetHeader("Content-Type").Should().Be(value); }
public void RequestIfNoneMatch() { const string value = "someguid"; var host = new DataServiceHostSimulator { RequestIfNoneMatch = value }; var requestMessage = new AstoriaRequestMessage(host); requestMessage.GetHeader("If-None-Match").Should().Be(value); }
public void GetAcceptableFormatTypesAcceptHeaderIsFromHost() { const string value = "some_value"; var host = new DataServiceHostSimulator { RequestAccept = value }; var requestMessage = new AstoriaRequestMessage(host); requestMessage.InitializeRequestVersionHeaders(V4); requestMessage.GetAcceptableContentTypes().Should().Be(value); }
public void AfterInitializeVerionFieldsAreSetToDefaults() { var host = new DataServiceHostSimulator { }; var requestMessage = new AstoriaRequestMessage(host); requestMessage.InitializeRequestVersionHeaders(V4); requestMessage.RequestVersion.Should().Be(V4); requestMessage.RequestMaxVersion.Should().Be(V4); }
public void ProcessExceptionShouldCallHost() { var callbackInvoked = false; var host = new DataServiceHostSimulator { }; host.ProcessExceptionCallBack = args => callbackInvoked = true; var requestMessage = new AstoriaRequestMessage(host); requestMessage.ProcessException(new HandleExceptionArgs(new Exception(), true, null, false)); callbackInvoked.Should().BeTrue(); }
public void HttpVerbIsFromHost() { foreach (var verb in HttpVerbUtils.KnownVerbs) { var host = new DataServiceHostSimulator { RequestHttpMethod = verb.ToString() }; var requestMessage = new AstoriaRequestMessage(host); requestMessage.HttpVerb.Should().Be(verb); requestMessage.RequestHttpMethod.Should().Be(verb.ToString()); } }
public void RequestHeadersIsFromHost() { var value = new WebHeaderCollection { { "RequestVersion", "2.0" }, { "Accept", "json" }, { "Custom", "customValue" } }; var host = new DataServiceHost2Simulator { RequestHeaders = value }; var requestMessage = new AstoriaRequestMessage(host); requestMessage.RequestHeaders.Should().BeEquivalentTo(value); requestMessage.RequestHeaders["RequestVersion"] = "2.0"; requestMessage.RequestHeaders["Accept"] = "json"; requestMessage.RequestHeaders["Custom"] = "customValue"; }
/// <summary> /// Initializes a new instance of <see cref="ODataMessageReaderDeserializer"/>. /// </summary> /// <param name="update">true if we're reading an update operation; false if not.</param> /// <param name="dataService">Data service for which the deserializer will act.</param> /// <param name="tracker">Tracker to use for modifications.</param> /// <param name="requestDescription">The request description to use.</param> /// <param name="enableODataServerBehavior">If true, the message reader settings will use the ODataServer behavior; /// if false, the message reader settings will use the default behavior.</param> internal ODataMessageReaderDeserializer(bool update, IDataService dataService, UpdateTracker tracker, RequestDescription requestDescription, bool enableODataServerBehavior) : base(update, dataService, tracker, requestDescription) { AstoriaRequestMessage requestMessage = dataService.OperationContext.RequestMessage; // WCF DS needs to treat content type */* as ATOM payload, so check for it here and override the content type header if (ContentTypeUtil.CompareMimeType(requestMessage.ContentType, XmlConstants.MimeAny)) { requestMessage.ContentType = XmlConstants.MimeApplicationAtom; } this.messageReader = new ODataMessageReader( requestMessage, WebUtil.CreateMessageReaderSettings(dataService, enableODataServerBehavior), dataService.Provider.GetMetadataProviderEdmModel()); }
/// <summary> /// Get the ETag header value from the request headers. /// </summary> /// <param name="operationContext">A reference to the context for the current operation.</param> /// <param name="etag"> /// The etag value sent by the client (as the value of an If[-None-]Match header) as part of the HTTP request sent to the data service /// This parameter will be null if no If[-None-]Match header was present /// </param> /// <param name="checkETagForEquality"> /// True if an value of the etag parameter was sent to the server as the value of an If-Match HTTP request header /// False if an value of the etag parameter was sent to the server as the value of an If-None-Match HTTP request header /// null if the HTTP request for the stream was not a conditional request /// </param> private static void GetETagFromHeaders(DataServiceOperationContext operationContext, out string etag, out bool?checkETagForEquality) { Debug.Assert(operationContext != null, "operationContext != null"); Debug.Assert(operationContext.RequestMessage != null, "operationContext.RequestMessage != null"); AstoriaRequestMessage host = operationContext.RequestMessage; Debug.Assert(string.IsNullOrEmpty(host.GetRequestIfMatchHeader()) || string.IsNullOrEmpty(host.GetRequestIfNoneMatchHeader()), "IfMatch and IfNoneMatch should not be both set."); if (string.IsNullOrEmpty(host.GetRequestIfMatchHeader()) && string.IsNullOrEmpty(host.GetRequestIfNoneMatchHeader())) { etag = null; checkETagForEquality = null; } else if (!string.IsNullOrEmpty(host.GetRequestIfMatchHeader())) { etag = host.GetRequestIfMatchHeader(); checkETagForEquality = true; } else { etag = host.GetRequestIfNoneMatchHeader(); checkETagForEquality = false; } }
/// <summary>Handles an exception when processing a batch response.</summary> /// <param name='service'>Data service doing the processing.</param> /// <param name="requestMessage">requestMessage holding information about the request that caused an error</param> /// <param name="responseMessage">responseMessage to which we need to write the exception message</param> /// <param name='exception'>Exception thrown.</param> /// <param name='batchWriter'>Output writer for the batch.</param> /// <param name="responseStream">Underlying response stream.</param> /// <param name="defaultResponseVersion">The data service version to use for response, if it cannot be computed from the requestMessage.</param> internal static void HandleBatchOperationError(IDataService service, AstoriaRequestMessage requestMessage, IODataResponseMessage responseMessage, Exception exception, ODataBatchWriter batchWriter, Stream responseStream, Version defaultResponseVersion) { Debug.Assert(service != null, "service != null"); Debug.Assert(exception != null, "exception != null"); Debug.Assert(batchWriter != null, "batchWriter != null"); Debug.Assert(service.Configuration != null, "service.Configuration != null"); Debug.Assert(CommonUtil.IsCatchableExceptionType(exception), "CommonUtil.IsCatchableExceptionType(exception)"); ErrorHandler handler = CreateHandler(service, requestMessage, exception, defaultResponseVersion); service.InternalHandleException(handler.exceptionArgs); if (requestMessage != null && responseMessage != null) { responseMessage.SetHeader(XmlConstants.HttpODataVersion, handler.responseVersion.ToString(2) + ";"); requestMessage.ProcessException(handler.exceptionArgs); // if ProcessBenignException returns anything, we can safely not write to the stream. if (ProcessBenignException(exception, service) != null) { return; } } if (requestMessage != null) { responseMessage = requestMessage.BatchServiceHost.GetOperationResponseMessage(); WebUtil.SetResponseHeadersForBatchRequests(responseMessage, requestMessage.BatchServiceHost); } else { responseMessage = batchWriter.CreateOperationResponseMessage(null); responseMessage.StatusCode = handler.exceptionArgs.ResponseStatusCode; } MessageWriterBuilder messageWriterBuilder = MessageWriterBuilder.ForError( null, service, handler.responseVersion, responseMessage, handler.contentType, null /*acceptCharsetHeaderValue*/); using (ODataMessageWriter messageWriter = messageWriterBuilder.CreateWriter()) { ODataError error = handler.exceptionArgs.CreateODataError(); WriteErrorWithFallbackForXml(messageWriter, handler.encoding, responseStream, handler.exceptionArgs, error, messageWriterBuilder); } }
private static ErrorHandler CreateHandler(IDataService service, AstoriaRequestMessage requestMessage, Exception exception, Version defaultResponseVersion) { Debug.Assert(service != null, "service != null"); Debug.Assert(service.Configuration != null, "service.Configuration != null"); string acceptableContentTypes = null; string requestAcceptCharsetHeader = null; Version responseVersion = defaultResponseVersion; if (requestMessage != null) { acceptableContentTypes = requestMessage.GetAcceptableContentTypes(); requestAcceptCharsetHeader = requestMessage.GetRequestAcceptCharsetHeader(); try { Version maxProtocolVersion = service.Configuration.DataServiceBehavior.MaxProtocolVersion.ToVersion(); requestMessage.InitializeRequestVersionHeaders(maxProtocolVersion); responseVersion = VersionUtil.GetResponseVersionForError(requestMessage.GetAcceptableContentTypes(), requestMessage.RequestMaxVersion, maxProtocolVersion); } catch (Exception e) { if (!CommonUtil.IsCatchableExceptionType(e)) { throw; } // Ignore exceptions as we should use the default response version. } } return new ErrorHandler(exception, service.Configuration.UseVerboseErrors, responseVersion, acceptableContentTypes, requestAcceptCharsetHeader); }
/// <summary> /// Creates a new instance of the RequestMessage and ResponseMessage to cache the request headers and to validate the data from the host interface. /// </summary> /// <remarks> /// Note that this code cannot go in the constructor, because it is possible for a user to attach a host to DataService, /// process a request, changes fields _on the same host_, and then call process request again. So we need to be able to /// create a new AstoriaRequestMessage while still using the same DataServiceOpeationContext. /// </remarks> /// <param name="dataService">The current data service instance.</param> internal void InitializeAndCacheHeaders(IDataService dataService) { Debug.Assert(this.hostInterface != null, "this.hostInterface != null"); this.requestMessage = new AstoriaRequestMessage(this.hostInterface); this.responseMessage = new AstoriaResponseMessage(this.hostInterface); this.CurrentDataService = dataService; // Add a "nosniff" content-type option to instruct IE8/9 not to sniff the content instead of rendering it // according to the specified content-type. This mitigates against XSS attacks such as embedded scripts in // content specified as text/plain. if (this.hostInterface is IDataServiceHost2) { this.ResponseHeaders.Add(XmlConstants.XContentTypeOptions, XmlConstants.XContentTypeOptionNoSniff); } }
public void AfterInitializeVerionFieldsAreSetWithHostValues() { var host = new DataServiceHostSimulator { RequestVersion = "4.0", RequestMaxVersion = "4.0" }; var requestMessage = new AstoriaRequestMessage(host); requestMessage.InitializeRequestVersionHeaders(V4); requestMessage.RequestVersion.Should().Be(V4); requestMessage.RequestMaxVersion.Should().Be(V4); }
/// <summary> /// Reads the parameters for the specified <paramref name="operation"/> from the <paramref name="host"/>. /// </summary> /// <param name="host">RequestMessage with request information.</param> /// <param name="operation">Operation with parameters to be read.</param> /// <returns>A new object[] with parameter values.</returns> private static object[] ReadOperationParameters(AstoriaRequestMessage host, OperationWrapper operation) { Debug.Assert(host != null, "host != null"); Debug.Assert(operation != null, "operation != null"); Debug.Assert(operation.Kind == OperationKind.ServiceOperation, "operation.Kind == OperationKind.ServiceOperation"); object[] operationParameters = new object[operation.Parameters.Count]; for (int i = 0; i < operation.Parameters.Count; i++) { Type parameterType = operation.Parameters[i].ParameterType.InstanceType; string queryStringValue = host.GetQueryStringItem(operation.Parameters[i].Name); operationParameters[i] = ParseOperationParameter(parameterType, queryStringValue); } return operationParameters; }