/** * The <body/> element of every client request MUST possess a sequential request ID encapsulated * via the 'rid' attribute. * * @param node node to validate * @param previous previous node */ private void assertRequestIDSequential(final Node node, final Node previous) { String ridStr = node.getBody().getAttribute(Attributes.RID); assertNotNull("Request ID attribute not present", ridStr); long rid = Long.parseLong(ridStr); if (previous != null) { String prevRidStr = previous.getBody().getAttribute(Attributes.RID); assertNotNull("Previous request ID attribute not present", prevRidStr); long prevRid = Long.parseLong(prevRidStr); assertEquals("Request ID is not sequential", prevRid + 1, rid); } }
/** * If the client will be including 'ack' attributes on requests during a session, then it MUST * include an 'ack' attribute (set to '1') in its session creation request, and set the 'ack' * attribute of requests throughout the session. * * @param idx message number (zero-based) * @param first first message sent * @param request message to validate */ private void validateSubsequestRequestAck(final int idx, final Node first, final Node request) { if (request == first) { // Nothing to check on first request return; } String ack = first.getBody().getAttribute(Attributes.ACK); if (ack == null) { String subAck = request.getBody().getAttribute(Attributes.ACK); assertNull( "subsequent request #" + idx + " can only use acks if " + "advertized in session creation request", subAck); } }
/** * The <body/> element of the first request SHOULD possess the following attributes (they SHOULD * NOT be included in any other requests except as specified under Adding Streams To A Session): * "to", "xml:lang", "ver", "wait", "hold". * * @param message number (zero-based) * @param request request message * @param previous previous request message */ private void validateRequestHeaders(final int idx, final Node request, final Node previous) { AbstractBody body = request.getBody(); if (previous == null) { // session creation request assertNotNull( "to attribute not present in request #" + idx, body.getAttribute(Attributes.TO)); assertNotNull( "xml:lang attribute not present in request #" + idx, body.getAttribute(Attributes.XML_LANG)); assertNotNull( "ver attribute not present in request #" + idx, body.getAttribute(Attributes.VER)); assertNotNull( "wait attribute not present in request #" + idx, body.getAttribute(Attributes.WAIT)); assertNotNull( "hold attribute not present in request #" + idx, body.getAttribute(Attributes.HOLD)); } else { // subsequent request assertNull("to attribute was present in request #" + idx, body.getAttribute(Attributes.TO)); assertNull( "xml:lang attribute was present in request #" + idx, body.getAttribute(Attributes.XML_LANG)); assertNull("ver attribute was present in request #" + idx, body.getAttribute(Attributes.VER)); assertNull( "wait attribute was present in request #" + idx, body.getAttribute(Attributes.WAIT)); assertNull( "hold attribute was present in request #" + idx, body.getAttribute(Attributes.HOLD)); } }
/** * Validate a request. * * @param message number (zero-based) * @param first message sent * @param previous previous request, or {@code null} if this is the first request ever sent * @param request request message */ private void validateRequest( final int idx, final Node first, final Node previous, final Node request) { try { assertValidXML(request); assertSingleBodyElement(request); assertNoComments(request); assertNoProcessingInstructions(request); assertRequestIDSequential(request, previous); if (previous == null) { validateRequestHeaders(idx, request, previous); assertSessionCreationRequestID(request); assertSessionCreationRequestIDRange(request); validateSessionCreationAck(idx, request); validateSessionCreationSID(idx, request); validateSessionCreationHold(idx, request); } else { validateRequestHeaders(idx, request, previous); validateSubsequentRequestSID(idx, request); validateSubsequestRequestAck(idx, first, request); validateSubsequentPause(idx, request, previous); } } catch (AssertionError err) { LOG.info( "Assertion failed for request #" + idx + ": " + err.getMessage() + "\n" + request.getBody().toXML()); throw (err); } }
/* * The client MUST generate a large, random, positive integer for the * initial 'rid' (see Security Considerations) and then increment that * value by one for each subsequent request. * * @param node node to validate */ private void assertSessionCreationRequestID(final Node node) { String ridStr = node.getBody().getAttribute(Attributes.RID); assertNotNull("Request ID attribute not present", ridStr); long rid = Long.parseLong(ridStr); assertTrue("RID was <= 0", rid > 0); // Not checking to see if it is "large" since it is already random }
/** * If the connection manager did not specify a 'maxpause' attribute at the start of the session * then the client MUST NOT send a 'pause' attribute during the session. * * @param idx message number (zero-based) * @param request message to validate */ private void validateSubsequentPause(final int idx, final Node request, final Node previous) { AbstractBody scr = sessionCreationResponse.get(); if (scr == null) { // Not checking this return; } try { // Check the current node: AttrPause pause = AttrPause.createFromString(request.getBody().getAttribute(Attributes.PAUSE)); if (pause != null) { AttrMaxPause maxPause = AttrMaxPause.createFromString(scr.getAttribute(Attributes.MAXPAUSE)); assertNotNull( "Request #" + idx + " can only use pause when " + "advertized in session creation response", maxPause); assertTrue(pause.intValue() < maxPause.intValue()); } // Check the previous node AttrPause prevPause = AttrPause.createFromString(previous.getBody().getAttribute(Attributes.PAUSE)); if (prevPause != null) { long delta = request.getTime() - previous.getTime(); if (delta > prevPause.getInMilliseconds()) { fail( "Request #" + idx + " was sent too late relative to " + "the previous pause message (delta=" + delta + ", pause=" + prevPause.getInMilliseconds() + ")"); } } } catch (BOSHException boshx) { fail("Could not parse pause/maxpause: " + boshx.getMessage()); } }
/* * The client MUST take care to choose an initial 'rid' that will never be * incremented above 9007199254740991 [21] within the session. * * @param node node to validate */ private void assertSessionCreationRequestIDRange(final Node node) { String ridStr = node.getBody().getAttribute(Attributes.RID); long rid = Long.parseLong(ridStr); BigInteger biRID = BigInteger.valueOf(rid); BigInteger biMax = new BigInteger("9007199254740991"); BigInteger biThreshold = BigInteger.valueOf(Math.round(Math.pow(2.0, 20.0))); assertTrue( "Initial RID leaves fewer than " + biThreshold.toString() + " total requests before max RID limit is hit", biRID.compareTo(biMax.subtract(biThreshold)) <= 0); }
/** * XEP-0124 Section 6: * * <p>The <body/> element and its content together MUST conform to the specifications set out in * XML 1.0 [14] * * <p>and... * * <p>They SHOULD also conform to Namespaces in XML [15]. * * <p>and... * * <p>The content MUST NOT contain Partial XML elements. * * @param request request to validate */ private void assertValidXML(final Node request) { ByteArrayInputStream stream = new ByteArrayInputStream(request.getBody().toXML().getBytes()); StreamSource source = new StreamSource(stream); Throwable thr; try { synchronized (VALIDATOR) { VALIDATOR.validate(source); } return; } catch (IOException iox) { thr = iox; } catch (SAXException saxx) { thr = saxx; } fail("Request XML validation failed: " + thr.getMessage()); }
/** * Checks that the validator didn't encounter any problems. If it did, it should perform the * assertions here. * * @param scr session creation response message to use in validation, or {@code null} to skip * those checks */ public void checkAssertions(final AbstractBody scr) { if (requests == null) { // Nothing to validate. LOG.fine("Nothing to validate"); return; } sessionCreationResponse.set(scr); LOG.fine("Validating " + requests.size() + " message(s)"); Node previous = null; Node first = null; int index = 0; for (Node node : requests) { if (first == null) { first = node; } String rid = node.getBody().getAttribute(Attributes.RID); LOG.fine("Validating msg #" + index + " (RID: " + rid + ")"); validateRequest(index, first, previous, node); previous = node; index++; } }
/** * Execute an XPath against a AbstractBody instance. * * @param body node with body to XPath against * @param xpath the XPath expression * @param returnType type to return * @return resulting node */ private Object bodyXPath(final Node node, final String xpath, final QName returnType) { try { String xml = node.getBody().toXML(); ByteArrayInputStream stream = new ByteArrayInputStream(xml.getBytes()); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(stream); XPathFactory xpf = XPathFactory.newInstance(); XPath xp = xpf.newXPath(); xp.setNamespaceContext( new NamespaceContext() { Map<String, String> map = new HashMap<String, String>(); { map.put("bosh", BodyQName.BOSH_NS_URI); map.put("xml", XMLConstants.XML_NS_URI); } public String getNamespaceURI(String prefix) { return map.get(prefix); } public String getPrefix(String namespaceURI) { throw new UnsupportedOperationException("Not supported."); } public Iterator getPrefixes(String namespaceURI) { throw new UnsupportedOperationException("Not supported."); } }); return xp.evaluate(xpath, document, returnType); } catch (Throwable thr) { fail("Could not parse body to DOM document: " + thr.getMessage()); } return null; }
/** * All requests after the first one MUST include a valid 'sid' attribute. * * @param message number (zero-based) * @param request request message * @param previous previous request, or {@code null} if this is the first request ever sent */ private void validateSubsequentRequestSID(final int idx, final Node request) { assertNotNull( "sid attribute not present in request #" + idx, request.getBody().getAttribute(Attributes.SID)); }
/** * If the client is not able to use HTTP Pipelining then the "hold" attribute SHOULD be set to * "1". * * @param message number (zero-based) * @param request request message */ private void validateSessionCreationHold(final int idx, final Node request) { String hold = request.getBody().getAttribute(Attributes.HOLD); assertNotNull("hold attribute was not present in initial request", hold); assertEquals("incorrect hold attrivute value", "1", hold); }
/** * A client MAY include an 'ack' attribute (set to "1") to indicate that it will be using * acknowledgements throughout the session If the client will be including 'ack' attributes on * requests during a session, then it MUST include an 'ack' attribute (set to '1') in its session * creation request, and set the 'ack' attribute of requests throughout the session. * * @param message number (zero-based) * @param request request message */ private void validateSessionCreationAck(final int idx, final Node request) { String ack = request.getBody().getAttribute(Attributes.ACK); if (ack != null) { assertEquals("intial ack must be 1", "1", ack); } }
/** * The initialization request is unique in that the <body/> element MUST NOT possess a 'sid' * attribute. * * @param message number (zero-based) * @param request request message */ private void validateSessionCreationSID(final int idx, final Node request) { assertNull( "sid attribute was present in request #" + idx, request.getBody().getAttribute(Attributes.SID)); }