/// <summary> /// Gets a memory stream to search in for the specified context /// </summary> /// <param name="dataSource"></param> /// <param name="header"></param> /// <param name="context"></param> /// <returns></returns> protected MemoryStream GetMemoryStream(ITrafficDataAccessor dataSource, TVRequestInfo header, SearchContext context) { MemoryStream toBeSearched; if (context == SearchContext.Request || context == SearchContext.RequestBody) { toBeSearched = new MemoryStream(dataSource.LoadRequestData(header.Id)); } else if (context == SearchContext.Response) { toBeSearched = new MemoryStream(dataSource.LoadResponseData(header.Id)); } else { toBeSearched = new MemoryStream(); byte[] data = dataSource.LoadRequestData(header.Id); toBeSearched.Write(data, 0, data.Length); data = Constants.DefaultEncoding.GetBytes(Environment.NewLine); toBeSearched.Write(data, 0, data.Length); data = dataSource.LoadResponseData(header.Id); toBeSearched.Write(data, 0, data.Length); //reset the prosition so readline can start from the beggining toBeSearched.Position = 0; } return(toBeSearched); }
private static void ReplaceTest(string replacement) { TrafficViewer.Instance.NewTvf(); ITrafficDataAccessor tvf = TrafficViewer.Instance.TrafficViewerFile; string firstRequest = "GET /a1 HTTP/1.1\r\nHeader1: a1"; string secondRequest = "GET /a2 HTTP/1.1\r\nHeader1: a2"; string firstResponse = "HTTP 200 OK\r\n<r>1</r>"; string secondResponse = "HTTP 200 OK\r\n<r>2</r><tag><r>3</r>"; tvf.AddRequestResponse(firstRequest, firstResponse); tvf.AddRequestResponse(secondRequest, secondResponse); Assert.AreEqual(2, tvf.RequestCount); LineSearcher searcher = new LineSearcher(); LineMatches result = new LineMatches(); SearchCriteriaSet criteriaSet = new SearchCriteriaSet(); criteriaSet.Add(new SearchCriteria(SearchContext.Full, true, @"a1|a=2|<r>\d</r>")); searcher.Search(tvf, criteriaSet, result); tvf.Replace(result, replacement); firstRequest = Constants.DefaultEncoding.GetString(tvf.LoadRequestData(0)); secondRequest = Constants.DefaultEncoding.GetString(tvf.LoadRequestData(1)); firstResponse = Constants.DefaultEncoding.GetString(tvf.LoadResponseData(0)); secondResponse = Constants.DefaultEncoding.GetString(tvf.LoadResponseData(1)); Assert.AreEqual("GET /" + replacement + " HTTP/1.1\r\nHeader1: " + replacement, firstRequest); Assert.AreEqual("HTTP 200 OK\r\n" + replacement + "<tag>" + replacement, secondResponse); }
private string GetText(int index) { StringBuilder sb = new StringBuilder(); byte[] bytes = _source.LoadRequestData(index); sb.Append(Constants.DefaultEncoding.GetString(bytes)); sb.Append(Environment.NewLine); sb.Append(Environment.NewLine); bytes = _source.LoadResponseData(index); sb.Append(Constants.DefaultEncoding.GetString(bytes)); return(sb.ToString()); }
void ValidateTrafficSourcesRequestsAreSame(ITrafficDataAccessor src1, ITrafficDataAccessor src2, bool includeResponses = true) { Assert.AreEqual(src1.RequestCount, src2.RequestCount); int i = -1, j = -1; while (true) { TVRequestInfo first = src1.GetNext(ref i); TVRequestInfo second = src2.GetNext(ref j); if (first == null && second == null) { break; } Assert.AreEqual(first.RequestLine, second.RequestLine); //proceed to compare http requests byte[] firstRequest = src1.LoadRequestData(first.Id); byte[] secondRequest = src2.LoadRequestData(second.Id); HttpRequestInfo firstRequestInfo = new HttpRequestInfo(firstRequest); HttpRequestInfo seconRequestInfo = new HttpRequestInfo(secondRequest); Assert.AreEqual(firstRequestInfo.ToString(), seconRequestInfo.ToString()); if (includeResponses) { //proceed to compare responses Assert.AreEqual(first.ResponseStatus, second.ResponseStatus); byte[] firstResponse = src1.LoadResponseData(first.Id); byte[] secondResponse = src2.LoadResponseData(second.Id); HttpResponseInfo firstResponseInfo = new HttpResponseInfo(firstResponse); HttpResponseInfo secondResponseInfo = new HttpResponseInfo(secondResponse); Assert.AreEqual(firstResponseInfo.ToString(), secondResponseInfo.ToString()); } } }
public void TestLineSearchFullRegex() { TrafficViewer.Instance.NewTvf(); ITrafficDataAccessor tvf = TrafficViewer.Instance.TrafficViewerFile; string firstRequest = "POST /a1 HTTP/1.1\r\nHeader1: a1\r\n\r\na=1"; string secondRequest = "POST /a2 HTTP/1.1\r\nHeader1: a2\r\n\r\na=2"; string firstResponse = "HTTP 200 OK\r\n<r>1</r>"; string secondResponse = "HTTP 200 OK\r\n<r>2</r>"; tvf.AddRequestResponse(firstRequest, firstResponse); string testValue = Constants.DefaultEncoding.GetString(tvf.LoadRequestData(0)); Assert.AreEqual(firstRequest, testValue, "Incorrect first request"); testValue = Constants.DefaultEncoding.GetString(tvf.LoadResponseData(0)); Assert.AreEqual(firstResponse, testValue, "Incorrect first response"); tvf.AddRequestResponse(secondRequest, secondResponse); Assert.AreEqual(2, tvf.RequestCount, "Correct number of requests not added"); LineSearcher searcher = new LineSearcher(); LineMatches result = new LineMatches(); SearchCriteriaSet criteriaSet = new SearchCriteriaSet(); criteriaSet.Add(new SearchCriteria(SearchContext.Full, true, "a=1|<r>2</r>")); searcher.Search(tvf, criteriaSet, result); Assert.AreEqual(2, result.Count); Assert.AreEqual(0, result[0].RequestId); Assert.AreEqual(1, result[1].RequestId); Assert.AreEqual(SearchContext.Request, result[0].Context); Assert.AreEqual(SearchContext.Response, result[1].Context); Assert.AreEqual("a=1", firstRequest.Substring(result[0].MatchCoordinatesList[0].MatchPosition, result[0].MatchCoordinatesList[0].MatchLength)); Assert.AreEqual("<r>2</r>", secondResponse.Substring(result[1].MatchCoordinatesList[0].MatchPosition, result[1].MatchCoordinatesList[0].MatchLength)); }
/// <summary> /// Loads the request and response data to the GUI /// </summary> /// <param name="requestId"></param> /// <param name="dataSource">Traffic viewer file or other data source</param> private void LoadRequest(int requestId) { byte[] requestBytes = _dataSource.LoadRequestData(requestId); TVRequestInfo reqInfo = _dataSource.GetRequestInfo(requestId); Encoding enc = Constants.DefaultEncoding; /* * if (reqInfo != null && reqInfo.Description.Contains("Binary")) * { * enc = new TrafficViewerEncoding(); * }*/ if (requestBytes != null) { _requestText = enc.GetString(requestBytes); } else { _requestText = String.Empty; } _responseBytes = _dataSource.LoadResponseData(requestId); if (_responseBytes != null) { _responseText = enc.GetString(_responseBytes); } else { _responseBytes = new byte[0]; _responseText = String.Empty; } _currentId = requestId; LoadSelectedView(); }
public HttpResponseInfo SendRequest(HttpRequestInfo requestInfo) { int idx = -1; TVRequestInfo currDataSourceInfo = null; byte[] response = null; while ((currDataSourceInfo = _mockSite.GetNext(ref idx)) != null) { if (String.Compare(currDataSourceInfo.RequestLine, requestInfo.RequestLine) == 0) { response = _mockSite.LoadResponseData(currDataSourceInfo.Id); break; } } HttpResponseInfo respInfo = null; if (response != null) { respInfo = new HttpResponseInfo(response); } return(respInfo); }
/// <summary> /// Calculates if the current response is different than the original response /// </summary> /// <param name="responseInfo"></param> /// <returns></returns> private bool IsDifferentThanOriginalResponse(HttpResponseInfo responseInfo) { bool isDifferent; if (!_trackingReqInfo.ResponseStatus.Equals(responseInfo.Status.ToString())) { isDifferent = true; } else if (ResponseLengthIsSignificantlyDifferent(responseInfo, _trackingReqInfo)) { isDifferent = true; } else //calculate response similarity { byte[] oldRespBytes = _dataStore.LoadResponseData(_trackingReqInfo.Id); string oldRespString = Constants.DefaultEncoding.GetString(oldRespBytes); double similarity = ASESimilarityAlgorithm.CalculateSimilarity(oldRespString, responseInfo.ToString()); isDifferent = similarity < 0.98; } return(isDifferent); }
protected override void Export(ITrafficDataAccessor source, System.IO.Stream stream, bool overwriteScheme, bool isSSL, string newHost, int newPort) { TVRequestInfo info; int i = -1; string overridenScheme = isSSL ? "https" : "http"; //using an xml writer for memory concerns XmlWriter writer = new XmlTextWriter(stream, Encoding.UTF8); writer.WriteStartDocument(); writer.WriteStartElement("StateInducer"); writer.WriteAttributeString("Version", "1.1"); writer.WriteStartElement("Sequences"); writer.WriteStartElement("Sequence"); writer.WriteAttributeString("Name", "Exported from X-Force Black Ops"); writer.WriteAttributeString("Enabled", "True"); writer.WriteAttributeString("ReplayOptimizationsEnabled", "False"); writer.WriteAttributeString("TestInIsolation", "True"); writer.WriteAttributeString("TestSingleThreadedly", "True"); writer.WriteAttributeString("ManualExploreEnabled", "False"); writer.WriteAttributeString("LoginRequired", "True"); writer.WriteStartElement("requests"); while ((info = source.GetNext(ref i)) != null) { string scheme = info.IsHttps ? "https" : "http"; scheme = overwriteScheme ? overridenScheme : scheme; //get the request information byte[] reqData = source.LoadRequestData(info.Id); byte[] respData = source.LoadResponseData(info.Id); if (reqData != null) { HttpRequestInfo reqInfo = new HttpRequestInfo(reqData); HttpResponseInfo respInfo = new HttpResponseInfo(respData); //add to the list of variables AddToVariableInfoCollection(reqInfo.Cookies.GetVariableInfoCollection()); AddToVariableInfoCollection(reqInfo.QueryVariables.GetVariableInfoCollection()); AddToVariableInfoCollection(reqInfo.BodyVariables.GetVariableInfoCollection()); AddToVariableInfoCollection(reqInfo.PathVariables.GetVariableInfoCollection()); WriteRequest(writer, newHost, newPort, scheme, reqInfo, respInfo); } } writer.WriteEndElement(); //writer.WriteRaw(Resources.VariableDefinitions); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndDocument(); writer.Flush(); writer.Close(); }
/// <summary> /// Actually gets a matching response from the mock data /// </summary> /// <param name="requestInfo"></param> /// <returns></returns> public HttpResponseInfo SendRequest(HttpRequestInfo requestInfo) { HttpResponseInfo responseInfo = null; string currentRequestString = requestInfo.ToString(); string currentAlertId = Utils.RegexFirstGroupValue(currentRequestString, ALERT_MATCH); TrafficServerMode currentMatchMode = _matchMode; if (!String.IsNullOrEmpty(currentAlertId)) //override the redundancy tuning if we are trying to match a alert { currentMatchMode = TrafficServerMode.BrowserFriendly; } //parse the request variables because we will need them to construct the hash requestInfo.ParseVariables(); TrafficServerResponseSet responseSet = null; //look in the server cache for the request ICacheable entry = TrafficServerCache.Instance.GetEntry(requestInfo.GetHashCode(currentMatchMode)); if (entry != null) { responseSet = entry.Reserve() as TrafficServerResponseSet; entry.Release(); } TrafficServerResponseSet similarRequests = new TrafficServerResponseSet(); if (responseSet == null) { //create a new empty response set responseSet = new TrafficServerResponseSet(); RequestSearcher searcher = new RequestSearcher(); SearchCriteriaSet criteriaSet; criteriaSet = GetCriteriaSet(requestInfo, currentMatchMode); RequestMatches matches = new RequestMatches(); //do the search! searcher.Search(_sourceStore, criteriaSet, matches); //normalize the matches and keep only the ones that have the same variables and values if (matches.Count > 0) { HttpRequestInfo original; HttpRequestInfo found; byte[] requestBytes; int i, n = matches.Count; for (i = 0; i < n & i < MATCHES_LIMIT; i++) { int match = matches[i]; TVRequestInfo header = _sourceStore.GetRequestInfo(match); if (_ignoreAuth) { if ( String.Compare(header.ResponseStatus, "401", true) == 0 || String.Compare(header.ResponseStatus, "407", true) == 0) { HttpServerConsole.Instance.WriteLine(LogMessageType.Warning, "Skipping authentication challenge"); //simply skip 401 matches continue; } } if (String.Compare(header.Description, Resources.TrafficLogProxyDescription, true) == 0) { //is likely that the source store is also the save store and this may be //the current request being saved HttpServerConsole.Instance.WriteLine(LogMessageType.Warning, "Skipping request to traffic store"); continue; } requestBytes = _sourceStore.LoadRequestData(match); string requestString = Constants.DefaultEncoding.GetString(requestBytes); if (String.IsNullOrEmpty(requestString)) { continue; //skip the current match is incorrect } original = new HttpRequestInfo(DynamicElementsRemover.Remove(currentRequestString)); found = new HttpRequestInfo(DynamicElementsRemover.Remove(requestString)); if (RequestMatcher.IsMatch(original, found, TrafficServerMode.Strict)) { responseSet.Add(match); } else if (currentMatchMode != TrafficServerMode.Strict && RequestMatcher.IsMatch(original, found, currentMatchMode)) { similarRequests.Add(match); } } //if no exact requests were found if (responseSet.Count == 0 && similarRequests.Count > 0) { HttpServerConsole.Instance.WriteLine (LogMessageType.Warning, "Warning, exact match was not found for {0} returning a similar request.", requestInfo.RequestLine); } responseSet.AddRange(similarRequests.Matches); } //add this response set to the cache TrafficServerCache.Instance.Add(requestInfo.GetHashCode(currentMatchMode), new CacheEntry(responseSet)); } //get the next response id from the response set int requestId = responseSet.GetNext(); if (requestId == NULL_INDEX) { HttpServerConsole.Instance.WriteLine(LogMessageType.Warning, "(404) Request not found: {0}" , requestInfo.RequestLine); //the request was not found at all, return a 404 return(new HttpResponseInfo(HttpErrorResponse.GenerateHttpErrorResponse(HttpStatusCode.NotFound, "Request Not Found", "<html><head><title>Error code: 404</title><body><h1>Request was not found or variables didn't match.</h1></body></html>"))); } if (requestId != NULL_INDEX) { HttpServerConsole.Instance.WriteLine(LogMessageType.Information, "Returning response from request id: {0}", requestId); byte[] responseBytes = _sourceStore.LoadResponseData(requestId); if (responseBytes != null) { responseInfo = new HttpResponseInfo(responseBytes); if (!String.IsNullOrEmpty(currentAlertId)) { Encoding encoding = HttpUtil.GetEncoding(responseInfo.Headers["Content-Type"]); string responseString = encoding.GetString(responseBytes); responseString = Utils.ReplaceGroups(responseString, ALERT_MATCH, currentAlertId); responseInfo = new HttpResponseInfo(encoding.GetBytes(responseString)); } } //add the request id header responseInfo.Headers.Add("Traffic-Store-Req-Id", requestId.ToString()); } return(responseInfo); }
/// <summary> /// Executes a diff operation /// </summary> /// <param name="firstRequestId"></param> /// <param name="secondRequestId"></param> /// <param name="source"></param> /// <returns></returns> public RequestsDifferResult Diff(int firstRequestId, int secondRequestId, ITrafficDataAccessor source) { RequestsDifferResult result = null; if (source.ContainsId(firstRequestId) && source.ContainsId(secondRequestId)) { //first retrieve the requests byte [] bytes1 = source.LoadRequestData(firstRequestId); byte [] bytes2 = source.LoadRequestData(secondRequestId); string firstRequest = String.Empty; string secondRequest = String.Empty; if (bytes1 != null) { firstRequest = GetString(bytes1); } if (bytes2 != null) { secondRequest = GetString(bytes2); } result = new RequestsDifferResult(); try //try to diff the request components { //diff the request line result = DiffFirstLine(result, firstRequest, secondRequest, 0, 0); //diff the request headers result = DiffHeaders(result, firstRequest, secondRequest, 0, 0); //next compare POST data if available result = DiffPostData(result, firstRequest, secondRequest); } catch (DiffException) //if the request are malformed use a regular letters differ { LettersDiffer lettersDiffer = new LettersDiffer(); DiffResult lettersResult = lettersDiffer.DoDiff(firstRequest, secondRequest); result.DiffsForFirst = lettersResult.DifferencesForFirst; result.DiffsForSecond = lettersResult.DifferencesForSecond; } //done with the requests calculate the base position for the response result.FirstText = firstRequest + "\n\n"; result.SecondText = secondRequest + "\n\n"; int firstRespBasePos = result.FirstText.Length; int secondRespBasePos = result.SecondText.Length; //load the responses bytes1 = source.LoadResponseData(firstRequestId); bytes2 = source.LoadResponseData(secondRequestId); string firstResponse = String.Empty; string secondResponse = String.Empty; if (bytes1 != null) { firstResponse = GetString(bytes1); } if (bytes2 != null) { secondResponse = GetString(bytes2); } try { //diff the status line result = DiffFirstLine(result, firstResponse, secondResponse, firstRespBasePos, secondRespBasePos); //diff the request headers result = DiffHeaders(result, firstResponse, secondResponse, firstRespBasePos, secondRespBasePos); //diff the response body result = DiffResponseBody(result, firstResponse, secondResponse, firstRespBasePos, secondRespBasePos); } catch (DiffException) { //if the responses are missing the headers use a regular lines differ LinesDiffer linesDiffer = new LinesDiffer(); linesDiffer.AddTask(firstResponse, firstRespBasePos); linesDiffer.AddTask(secondResponse, secondRespBasePos); linesDiffer.DoDiff(); result.DiffsForFirst = linesDiffer.GetResultingDifferences(0); result.DiffsForSecond = linesDiffer.GetResultingDifferences(1); } //append the responses to the result text result.FirstText += firstResponse; result.SecondText += secondResponse; //sort and merge the resulting differences result.DiffsForFirst.MergeAll(); result.DiffsForSecond.MergeAll(); } return(result); }
protected override void Export(ITrafficDataAccessor source, System.IO.Stream stream, bool overwriteScheme, bool isSSL, string newHost, int newPort) { TVRequestInfo info, inSessionRequestInfo = null; int i = -1, count = 0; string overridenScheme = isSSL ? "https" : "http"; //using an xml writer for memory concerns XmlWriter writer = new XmlTextWriter(stream, Encoding.UTF8); writer.WriteStartDocument(); writer.WriteStartElement("SessionManagement"); writer.WriteAttributeString("Version", "1.2"); writer.WriteElementString("SessionManagementMode", "Manual"); writer.WriteElementString("AllowConcurrentLogins", "True"); writer.WriteElementString("EnableJSXInLoginReplay", "False"); writer.WriteStartElement("RecordedSessionRequests"); bool loginFound = false; while ((info = source.GetNext(ref i)) != null) { string scheme = info.IsHttps ? "https" : "http"; scheme = overwriteScheme ? overridenScheme : scheme; //get the request information byte[] reqData = source.LoadRequestData(info.Id); byte[] respData = source.LoadResponseData(info.Id); if (reqData != null) { HttpRequestInfo reqInfo = new HttpRequestInfo(reqData); reqInfo.IsSecure = scheme.Equals("https"); HttpResponseInfo respInfo = new HttpResponseInfo(respData); //add to the list of variables AddToVariableInfoCollection(reqInfo.Cookies.GetVariableInfoCollection()); AddToVariableInfoCollection(reqInfo.QueryVariables.GetVariableInfoCollection()); AddToVariableInfoCollection(reqInfo.BodyVariables.GetVariableInfoCollection()); AddToVariableInfoCollection(reqInfo.PathVariables.GetVariableInfoCollection()); if (info.Description.IndexOf(Resources.Login, StringComparison.OrdinalIgnoreCase) != -1) { WriteRequest(writer, newHost, newPort, scheme, reqInfo, respInfo, new KeyValuePair <string, string>("SessionRequestType", "Login")); loginFound = true; count++; } else if (info.Description.IndexOf(Resources.Session, StringComparison.OrdinalIgnoreCase) != -1) { WriteRequest(writer, newHost, newPort, scheme, reqInfo, respInfo, new KeyValuePair <string, string>("IsSessionVerifier", "True")); inSessionRequestInfo = info; break; } else if (loginFound) { break; } } } writer.WriteEndElement(); if (inSessionRequestInfo != null) { writer.WriteStartElement("SessionVerifier"); writer.WriteElementString("Enable", "True"); writer.WriteElementString("Pattern", @"(?i)((log|sign)\s?(out|off)|exit|quit)"); writer.WriteElementString("PatternType", "RegularExpression"); byte[] reqData = source.LoadRequestData(inSessionRequestInfo.Id); byte[] respData = source.LoadResponseData(inSessionRequestInfo.Id); string scheme = inSessionRequestInfo.IsHttps ? "https" : "http"; scheme = overwriteScheme ? overridenScheme : scheme; WriteRequest(writer, newHost, newPort, scheme, new HttpRequestInfo(reqData), new HttpResponseInfo(respData), new KeyValuePair <string, string>("SessionRequestType", "Regular")); writer.WriteEndElement(); writer.WriteElementString("InSessionRequestIndex", count.ToString()); } //WriteVariableDefinitions(writer); writer.WriteEndElement(); writer.WriteEndDocument(); writer.Close(); if (!loginFound) { //warn the user throw new Exception(Resources.NoLoginRequestsFound); } }
/// <summary> /// Verifies that a request matches /// </summary> /// <param name="dataSource"></param> /// <param name="header"></param> /// <param name="criteriaSet"></param> /// <param name="result"></param> protected override void RequestMatches(ITrafficDataAccessor dataSource, TVRequestInfo header, SearchCriteriaSet criteriaSet, ISearchResult result) { bool found = true; int hashSum = criteriaSet.DescriptionFilter.GetHashCode(); if (criteriaSet.DescriptionFilter == null || criteriaSet.DescriptionFilter == String.Empty || header.Description.IndexOf(criteriaSet.DescriptionFilter, StringComparison.CurrentCultureIgnoreCase) > -1) { //apply an AND operation with all the search criterias from the last search criteria cached int i, n = criteriaSet.Count, start = criteriaSet.StartCriteriaIndex; for (i = start; i < n; i++) { ISearchCriteria criteria = criteriaSet[i]; //compose the text to search string toBeSearched; if (criteria.Context == SearchContext.RequestLine) { toBeSearched = header.RequestLine; } else if (criteria.Context == SearchContext.RequestBody || criteria.Context == SearchContext.Request) { toBeSearched = Constants.DefaultEncoding.GetString(dataSource.LoadRequestData(header.Id)); if (criteria.Context == SearchContext.RequestBody) { HttpRequestInfo reqInfo = new HttpRequestInfo(toBeSearched); toBeSearched = reqInfo.ContentDataString; } } else if (criteria.Context == SearchContext.Response) { toBeSearched = Constants.DefaultEncoding.GetString(dataSource.LoadResponseData(header.Id)); } else { string request = Constants.DefaultEncoding.GetString(dataSource.LoadRequestData(header.Id)); string response = Constants.DefaultEncoding.GetString(dataSource.LoadResponseData(header.Id)); StringBuilder sb = new StringBuilder(request.Length + response.Length); sb.Append(request); sb.Append(response); toBeSearched = sb.ToString(); } found = criteria.Matches(toBeSearched); if (found) //if found is still true cache the request id as a match for the current set of criterias { hashSum = hashSum ^ criteria.GetHashCode(); ICacheable entry = SearchSubsetsCache.Instance.GetEntry(hashSum); SearchSubset subset; if (entry == null) { subset = new SearchSubset(); subset.Add(header.Id); SearchSubsetsCache.Instance.Add(hashSum, new CacheEntry(subset)); } else { subset = entry.Reserve() as SearchSubset; //if (!subset.Contains(header.Id)) //not checking if the entry exists for performance reasons //{ subset.Add(header.Id); //} entry.Release(); } } else { break; //criteria didn't match, break the loop } } //end of for, all the criterias were matched or one criteria did not match if (found) { //add all the temp matches to the results result.Add(header.Id); } } //end description check }