Пример #1
0
        /// <summary>
        /// Gets the serialization format of the payload in the incoming request based on the content-type header.
        /// According to the HTTP header RFC http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html, the content type
        /// has the following format:
        ///
        ///         "Content-Type" ":" media-type
        ///         media-type     = type "/" subtype *( ";" parameter )
        ///
        /// We ignore the media-type parameter.
        /// </summary>
        internal SyncSerializationFormat GetRequestContentSerializationFormat()
        {
            if (String.IsNullOrEmpty(_operationContext.IncomingRequest.ContentType))
            {
                throw SyncServiceException.CreateBadRequestError(Strings.NoContentTypeInHeader);
            }

            // Get type /subtype without the media type parameter
            string[] contentTypeParts = _operationContext.IncomingRequest.ContentType.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

            if (0 == contentTypeParts.Length)
            {
                throw SyncServiceException.CreateBadRequestError(Strings.UnsupportedContentType);
            }

            if (contentTypeParts[0].Equals(CONTENT_TYPE_APPLICATION_ATOM, StringComparison.OrdinalIgnoreCase))
            {
                return(SyncSerializationFormat.ODataAtom);
            }

            if (contentTypeParts[0].Equals(CONTENT_TYPE_APPLICATION_JSON, StringComparison.OrdinalIgnoreCase))
            {
                return(SyncSerializationFormat.ODataJson);
            }

            throw SyncServiceException.CreateBadRequestError(Strings.UnsupportedContentType);
        }
Пример #2
0
        /// <summary>
        /// Gets the current request type.
        /// </summary>
        /// <returns>Request command type</returns>
        private RequestCommand GetRequestCommandType()
        {
            // Check if this is a request for $syncscopes
            if (_serviceHost.RelativeUriSegments.Length == 1 &&
                0 == String.Compare(_serviceHost.RelativeUriSegments[0], SyncServiceConstants.SYNC_SCOPES_URL_SEGMENT, StringComparison.InvariantCultureIgnoreCase))
            {
                // Only GET is allowed for $syncscopes, throw an error if the request http method is not GET.
                if (0 != String.Compare(_serviceHost.RequestHttpMethod, SyncServiceConstants.HTTP_VERB_GET, StringComparison.InvariantCultureIgnoreCase))
                {
                    throw SyncServiceException.CreateMethodNotAllowed(Strings.InvalidHttpMethodForSyncScopesRequest, SyncServiceConstants.HTTP_VERB_GET);
                }

                return(RequestCommand.SyncScopes);
            }

            // The item at index 1 of the RelativeUriSegments array contains the operation name.
            if (_serviceHost.RelativeUriSegments.Length < 2 || String.IsNullOrEmpty(_serviceHost.RelativeUriSegments[1].Trim()))
            {
                throw SyncServiceException.CreateBadRequestError(Strings.MissingOperationNameinRequest);
            }

            RequestCommand requestCommand;
            string         operation = _serviceHost.RelativeUriSegments[1];

            switch (operation.ToLowerInvariant())
            {
            case SyncServiceConstants.SYNC_OPERATION_UPLOAD_CHANGES:
                // Only POST is allowed for upload changes, throw an error if the request http method is not POST.
                if (0 != String.Compare(_serviceHost.RequestHttpMethod, SyncServiceConstants.HTTP_VERB_POST, StringComparison.InvariantCultureIgnoreCase))
                {
                    throw SyncServiceException.CreateMethodNotAllowed(Strings.InvalidHttpMethodForUploadChangesRequest, SyncServiceConstants.HTTP_VERB_GET);
                }

                requestCommand = RequestCommand.UploadChanges;

                break;

            case SyncServiceConstants.SYNC_OPERATION_DOWNLOAD_CHANGES:
                requestCommand = RequestCommand.DownloadChanges;

                break;

            case SyncServiceConstants.SYNC_OPERATION_SCOPE_METADATA:
                // Only GET is allowed for $metadata, throw an error if the request http method is not GET.
                if (0 != String.Compare(_serviceHost.RequestHttpMethod, SyncServiceConstants.HTTP_VERB_GET, StringComparison.InvariantCultureIgnoreCase))
                {
                    throw SyncServiceException.CreateMethodNotAllowed(Strings.InvalidHttpMethodForScopeMetadataRequest, SyncServiceConstants.HTTP_VERB_GET);
                }

                requestCommand = RequestCommand.ScopeMetadata;

                break;

            default:
                throw SyncServiceException.CreateBadRequestError(Strings.InvalidOperationName);
            }

            return(requestCommand);
        }
Пример #3
0
        /// <summary>
        /// Gets the scope name from the query string.
        /// </summary>
        /// <returns>scope name</returns>
        private string GetScopeName()
        {
            // The item at index 0 of the RelativeUriSegments array contains the scope name.
            if (_serviceHost.RelativeUriSegments.Length < 1 || String.IsNullOrEmpty(_serviceHost.RelativeUriSegments[0].Trim()))
            {
                throw SyncServiceException.CreateBadRequestError(Strings.MissingScopeNameInRequest);
            }

            // Scope names are compared in a case insensitive manner and used as keys in lower case.
            return(_serviceHost.RelativeUriSegments[0].ToLowerInvariant());
        }
Пример #4
0
        internal static string GetContentType(SyncSerializationFormat format)
        {
            switch (format)
            {
            case SyncSerializationFormat.ODataAtom:
                return("application/atom+xml");

            case SyncSerializationFormat.ODataJson:
                return("application/json");

            default:
                throw SyncServiceException.CreateBadRequestError("Unsupported serialization format");
            }
        }
Пример #5
0
        /// <summary>
        /// Verify query parameters for '$' etc.
        /// </summary>
        internal void VerifyQueryParameters()
        {
            NameValueCollection values = _operationContext.IncomingRequest.UriTemplateMatch.QueryParameters;

            var queryDictionary = new Dictionary <string, string>();

            foreach (string key in values.Keys)
            {
                if (!queryDictionary.ContainsKey(key))
                {
                    queryDictionary.Add(key, values[key]);
                }
                else
                {
                    throw SyncServiceException.CreateBadRequestError(Strings.DuplicateParametersInRequestUri);
                }
            }
        }
Пример #6
0
        /// <summary>
        /// Get a dictionary that contains command parameters.
        /// </summary>
        /// <param name="queryStringCollection">Querystring represented as a NameValueCollection</param>
        /// <returns>Dictionary of command parameter types and values.</returns>
        private Dictionary <CommandParamType, object> GetCommandParameters(NameValueCollection queryStringCollection)
        {
            var commandParams = new Dictionary <CommandParamType, object>();

            Dictionary <string, string> filterParams = GetFilterParamsFromIncomingRequest(queryStringCollection);

            commandParams.Add(CommandParamType.FilterParameters, filterParams);

            string scopeName = GetScopeName();

            // Check the scope requested against the list of enabled scopes.
            if (!_configuration.ScopeNames.Contains(scopeName.ToLowerInvariant()))
            {
                throw SyncServiceException.CreateBadRequestError(Strings.SyncScopeNotSupported);
            }

            commandParams.Add(CommandParamType.ScopeName, scopeName);

            return(commandParams);
        }
Пример #7
0
        internal Request(RequestCommand requestCommand,
                         HttpContextServiceHost serviceHost,
                         Dictionary <CommandParamType, object> commandParams,
                         byte[] blob,
                         List <IOfflineEntity> entities,
                         SyncSerializationFormat responseSerializationFormat)
        {
            IdToTempIdMapping = new Dictionary <string, string>();

            RequestCommand = requestCommand;
            ServiceHost    = serviceHost;
            CommandParams  = commandParams;
            SyncBlob       = blob;
            ResponseSerializationFormat = responseSerializationFormat;

            if (null != entities && requestCommand != RequestCommand.UploadChanges)
            {
                throw SyncServiceException.CreateBadRequestError(Strings.EntitiesOnlyAllowedForUploadChangesRequest);
            }

            EntityList = entities;
        }
Пример #8
0
        /// <summary>
        /// Validate the HTTP Verb for GET/POST and check if the URL matches the allowed format.
        /// </summary>
        internal void ValidateRequestHttpVerbAndSegments()
        {
            if (_operationContext == null)
            {
                throw SyncServiceException.CreateBadRequestError(Strings.NullWebOperationContext);
            }

            // Only allow GET and POST verbs
            if (0 != String.Compare(RequestHttpMethod, "GET", StringComparison.InvariantCultureIgnoreCase) &&
                0 != String.Compare(RequestHttpMethod, "POST", StringComparison.InvariantCultureIgnoreCase))
            {
                SyncTracer.Warning("Request HTTP method is not GET or POST. HTTP Method: {0}", RequestHttpMethod ?? String.Empty);
                throw SyncServiceException.CreateMethodNotAllowed(Strings.UnsupportedHttpMethod, "GET, POST");
            }

            // Check if we have less than 2 relative segments. The maximum segments we can have is 2
            // which is for /scope/operation.
            if (RelativeUriSegments.Length > 2)
            {
                throw SyncServiceException.CreateBadRequestError(Strings.InvalidUrlFormat);
            }
        }
Пример #9
0
        /// <summary>
        /// Get the value for a query string item.
        /// </summary>
        /// <param name="item">Item to search for in the incoming request uri.</param>
        /// <returns>Value of the item.</returns>
        internal string GetQueryStringItem(string item)
        {
            NameValueCollection values = _operationContext.IncomingRequest.UriTemplateMatch.QueryParameters;

            // Check if we have the item by using a direct function (good performance)
            // Note: the underlying data structure in a NameValueCollection is a hashtable.
            string[] strArray = values.GetValues(item);

            // If no match found, then loop through all keys and do a case insensitive search.
            if ((strArray == null) || (strArray.Length == 0))
            {
                string str = null;
                foreach (string str2 in values.Keys)
                {
                    if ((str2 != null) && StringComparer.OrdinalIgnoreCase.Equals(str2.Trim(), item))
                    {
                        if (str != null)
                        {
                            // Bad request
                            throw SyncServiceException.CreateBadRequestError(Strings.DuplicateParametersInRequestUri);
                        }
                        str      = str2;
                        strArray = values.GetValues(str2);
                    }
                }
                if ((strArray == null) || (strArray.Length == 0))
                {
                    return(null);
                }
            }

            if (strArray.Length != 1)
            {
                // syntax error, cannot have multiple querystring parameters with the same name.
                throw SyncServiceException.CreateBadRequestError(Strings.DuplicateParametersInRequestUri);
            }

            return(strArray[0]);
        }
Пример #10
0
        /// <summary>
        /// Parse the Id string and populate key fields of the object. This is called when the client sends tombstones
        /// and the Key fields are not present in the input payload.
        /// The approach used is to parse each key field individually and set it to a property with the same name.
        /// For example: For http://host/service.svc/Tag(ID=1), we parse out ID=1 and then populate the ID property of the targetObject with the
        /// value 1.
        /// </summary>
        /// <param name="entity">Entity for which we need to set the key fields.</param>
        /// <param name="serviceBaseUri">
        /// Base Uri of the service. The ServiceMetadata.Id property has the Uri which we want to strip off before attempting to
        /// parse the keys and values.
        /// </param>
        internal static void ParseIdStringAndPopulateKeyFields(IOfflineEntity entity, Uri serviceBaseUri)
        {
            Debug.Assert(null != entity);
            Debug.Assert(!String.IsNullOrEmpty(entity.ServiceMetadata.Id));

            string idString = entity.ServiceMetadata.Id;

            // Remove the ServiceUri and the entity type name from the EntityId
            // Note: Case sensitive comparisons are made since the client isn't supposed to change the Id for an
            // entity.
            string serviceUriWithTableName = serviceBaseUri + "/" + entity.GetType().Name;

            // If the Id does not have the correct format of serviceUri/TableName, then we should not continue further.
            if (!idString.StartsWith(serviceUriWithTableName, false, CultureInfo.InvariantCulture))
            {
                throw SyncServiceException.CreateBadRequestError(String.Format(Strings.EntityIdFormatIsIncorrect, idString));
            }

            // Remove the host and the table name from the Id.
            // Example: http://host/service.svc/table(id=123) will become (id=123)
            idString = idString.Remove(0, serviceUriWithTableName.Length);

            // Remove leading '/' if any. After this the id string is of the format (ID=1) or (ID=guid'<guidValue>')
            // If there are multiple Id values then, they are comma separated.));))
            if (idString.StartsWith("/"))
            {
                idString = idString.Substring(1);
            }

            // Make sure the ( and ) parenthesis exist.
            if (String.IsNullOrEmpty(idString) || idString[0] != '(' || idString[idString.Length - 1] != ')')
            {
                throw SyncServiceException.CreateBadRequestError(String.Format(Strings.EntityIdFormatIsIncorrect, entity.ServiceMetadata.Id));
            }

            // Remove the ( and ) characters.
            idString = idString.Substring(1, idString.Length - 2);

            // Get the key properties for the entity.
            var keyFieldPropertyInfoList = ReflectionUtility.GetPrimaryKeysPropertyInfoMapping(entity.GetType());

            // Split the string and get individual keyvalue pair strings. They key and value are still a single string separated by '='.
            // for types such as Guid, the value will be prefixed with 'guid'.
            string[] primaryKeyValuePair = idString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            // Throw if there is a mismatch between the key count of the entity and that passed in the URI.
            if (primaryKeyValuePair.Length != keyFieldPropertyInfoList.Length)
            {
                throw SyncServiceException.CreateBadRequestError(String.Format(Strings.BadRequestKeyCountMismatch, entity.ServiceMetadata.Id, entity.GetType()));
            }

            // At this point, we have key value pairs of the form "ID=1".
            foreach (var keyValuePair in primaryKeyValuePair)
            {
                // example: ID=1
                string[] keyValue = keyValuePair.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries);

                // Every key should have only 2 components.
                Debug.Assert(2 == keyValue.Length);

                // Get the property from the key list.
                string key          = keyValue[0].Trim();
                var    propertyInfo = keyFieldPropertyInfoList.Where(p => p.Name == key).FirstOrDefault();

                if (null == propertyInfo)
                {
                    throw SyncServiceException.CreateBadRequestError(
                              String.Format(Strings.BadRequestKeyNotFoundInResource, key, entity.ServiceMetadata.Id, entity.GetType()));
                }

                // Get typed value of the value.
                object targetValue;
                // Parse the value based on the target type.
                if (!ODataIdParser.TryKeyStringToPrimitive(keyValue[1], propertyInfo.PropertyType, out targetValue))
                {
                    throw SyncServiceException.CreateBadRequestError(
                              String.Format(Strings.UnableToParseKeyValueForProperty, keyValuePair, entity.ServiceMetadata.Id));
                }

                // Set the property value.
                propertyInfo.SetValue(entity, targetValue, null);
            }
        }
Пример #11
0
        /// <summary>
        /// Read and parse the incoming request stream for a POST request.
        /// </summary>
        private void ReadIncomingRequestStreamForPost()
        {
            if (null == _serviceHost.RequestStream || !_serviceHost.RequestStream.CanRead)
            {
                SyncTracer.Info("Request stream for HTTP POST is empty, null or cannot be read.");
                return;
            }

            try
            {
                var reader = WebUtil.GetSyncReader(_serviceHost.GetRequestContentSerializationFormat(),
                                                   _serviceHost.RequestStream,
                                                   _configuration.TypeToTableGlobalNameMapping.Keys.ToArray());

                reader.Start();

                while (reader.Next())
                {
                    switch (reader.ItemType)
                    {
                    case ReaderItemType.Entry:
                        IOfflineEntity entity = reader.GetItem();

                        if (entity.ServiceMetadata.IsTombstone)
                        {
                            if (String.IsNullOrEmpty(entity.ServiceMetadata.Id))
                            {
                                throw SyncServiceException.CreateBadRequestError(Strings.TombstoneEntityHasNoId);
                            }

                            WebUtil.ParseIdStringAndPopulateKeyFields(entity, _serviceHost.ServiceBaseUri);
                        }

                        _entityList.Add(entity);

                        bool hasTempId = false;
                        if (reader.HasTempId())
                        {
                            // Save the entity id to tempId mapping for use later when writing response.
                            _idToTempIdMapping.Add(WebUtil.GenerateOfflineEntityId(entity), reader.GetTempId());

                            hasTempId = true;
                        }

                        // Make sure, we have atleast one of Id or TempId
                        if (String.IsNullOrEmpty(entity.ServiceMetadata.Id) && !hasTempId)
                        {
                            throw SyncServiceException.CreateBadRequestError(Strings.BothIdAndTempIdAreMissing);
                        }

                        break;

                    case ReaderItemType.SyncBlob:
                        _syncBlob = reader.GetServerBlob();

                        break;
                    }
                }
            }
            catch (XmlException exception)
            {
                SyncTracer.Warning("XmlException: {0}", WebUtil.GetExceptionMessage(exception));

                throw SyncServiceException.CreateBadRequestError(Strings.BadRequestPayload);
            }
        }