From 35a64008a1e32647909641ab2375f100a2d97df7 Mon Sep 17 00:00:00 2001 From: junmingyang Date: Tue, 28 Sep 2021 10:36:50 +0800 Subject: [PATCH] add WWWAuthenticateHeader --- .../java/io/pkts/packet/sip/SipMessage.java | 227 ++++++++--------- .../sip/header/AddressParametersHeader.java | 3 + .../io/pkts/packet/sip/header/CSeqHeader.java | 1 + .../io/pkts/packet/sip/header/SipHeader.java | 188 ++++++++------ .../sip/header/WWWAuthenticateHeader.java | 92 +++++++ .../impl/AddressParametersHeaderImpl.java | 1 + .../impl/WWWAuthenticateHeaderImpl.java | 184 ++++++++++++++ .../packet/sip/impl/ImmutableSipMessage.java | 20 +- .../packet/sip/impl/SipMessageBuilder.java | 33 ++- .../io/pkts/packet/sip/impl/SipParser.java | 233 ++++++++++-------- .../io/pkts/packet/sip/SipRequestTest.java | 168 +++++++++++-- .../impl/WWWAuthenticateHeaderImplTest.java | 77 ++++++ 12 files changed, 873 insertions(+), 354 deletions(-) create mode 100644 pkts-sip/src/main/java/io/pkts/packet/sip/header/WWWAuthenticateHeader.java create mode 100644 pkts-sip/src/main/java/io/pkts/packet/sip/header/impl/WWWAuthenticateHeaderImpl.java create mode 100644 pkts-sip/src/test/java/io/pkts/packet/sip/header/impl/WWWAuthenticateHeaderImplTest.java diff --git a/pkts-sip/src/main/java/io/pkts/packet/sip/SipMessage.java b/pkts-sip/src/main/java/io/pkts/packet/sip/SipMessage.java index 9afb7f7b..eb2bacee 100644 --- a/pkts-sip/src/main/java/io/pkts/packet/sip/SipMessage.java +++ b/pkts-sip/src/main/java/io/pkts/packet/sip/SipMessage.java @@ -3,20 +3,7 @@ import io.pkts.buffer.Buffer; import io.pkts.buffer.Buffers; import io.pkts.packet.sip.address.SipURI; -import io.pkts.packet.sip.header.AddressParametersHeader; -import io.pkts.packet.sip.header.CSeqHeader; -import io.pkts.packet.sip.header.CallIdHeader; -import io.pkts.packet.sip.header.ContactHeader; -import io.pkts.packet.sip.header.ContentLengthHeader; -import io.pkts.packet.sip.header.ContentTypeHeader; -import io.pkts.packet.sip.header.ExpiresHeader; -import io.pkts.packet.sip.header.FromHeader; -import io.pkts.packet.sip.header.MaxForwardsHeader; -import io.pkts.packet.sip.header.RecordRouteHeader; -import io.pkts.packet.sip.header.RouteHeader; -import io.pkts.packet.sip.header.SipHeader; -import io.pkts.packet.sip.header.ToHeader; -import io.pkts.packet.sip.header.ViaHeader; +import io.pkts.packet.sip.header.*; import io.pkts.packet.sip.impl.SipInitialLine; import io.pkts.packet.sip.impl.SipParser; @@ -35,9 +22,8 @@ /** * Packet representing a SIP message. - * + * * @author jonas@jonasborjesson.com - * */ public interface SipMessage extends Cloneable { @@ -46,7 +32,7 @@ public interface SipMessage extends Cloneable { /** * The first line of a sip message, which is either a request or a response * line - * + * * @return */ Buffer getInitialLine(); @@ -56,15 +42,14 @@ public interface SipMessage extends Cloneable { /** * Got tired of casting the {@link SipMessage} into a {@link SipRequest} so * you can use this method instead. Just a short cut for: - * + * * - * (SipRequest)sipMessage; + * (SipRequest)sipMessage; * - * + * * @return this but casted into a {@link SipRequest} - * @throws ClassCastException - * in case this {@link SipMessage} is actually a - * {@link SipResponse}. + * @throws ClassCastException in case this {@link SipMessage} is actually a + * {@link SipResponse}. */ default SipRequest toRequest() throws ClassCastException { throw new ClassCastException("Unable to cast a " + this.getClass().getName() + " into a " + SipRequest.class.getName()); @@ -73,15 +58,14 @@ default SipRequest toRequest() throws ClassCastException { /** * Got tired of casting the {@link SipMessage} into a {@link SipResponse} so * you can use this method instead. Just a short cut for: - * + * * - * (SipResponse)sipMessage; + * (SipResponse)sipMessage; * - * + * * @return this but casted into a {@link SipResponse} - * @throws ClassCastException - * in case this {@link SipMessage} is actually a - * {@link SipResponse}. + * @throws ClassCastException in case this {@link SipMessage} is actually a + * {@link SipResponse}. */ default SipResponse toResponse() throws ClassCastException { throw new ClassCastException("Unable to cast a " + this.getClass().getName() + " into a " + SipResponse.class.getName()); @@ -99,12 +83,11 @@ default SipResponse toResponse() throws ClassCastException { *
  • {@link CSeqHeader}
  • *
  • {@link ViaHeader}
  • * - * + * * @param responseCode * @return - * @throws SipParseException - * in case anything goes wrong when parsing out headers from the - * {@link SipRequest} + * @throws SipParseException in case anything goes wrong when parsing out headers from the + * {@link SipRequest} */ default SipResponse.Builder createResponse(final int responseCode) throws SipParseException, ClassCastException { return createResponse(responseCode, null); @@ -116,7 +99,7 @@ default SipResponse.Builder createResponse(int responseCode, Buffer content) thr /** * Check whether this sip message is a response or not - * + * * @return */ default boolean isResponse() { @@ -125,7 +108,7 @@ default boolean isResponse() { /** * Check whether this sip message is a request or not - * + * * @return */ default boolean isRequest() { @@ -282,24 +265,23 @@ default boolean isTimeout() { /** * Checks whether this {@link SipMessage} is carrying anything in its * message body. - * + * * @return true if this {@link SipMessage} has a message body, false - * otherwise. + * otherwise. */ boolean hasContent(); /** * Get the method of this sip message - * + * * @return */ Buffer getMethod() throws SipParseException; /** * Get the header as a buffer - * - * @param headerName - * the name of the header we wish to fetch + * + * @param headerName the name of the header we wish to fetch * @return the header as a {@link SipHeader} or null if not found * @throws SipParseException */ @@ -307,9 +289,8 @@ default boolean isTimeout() { /** * Same as {@link #getHeader(Buffers.wrap(keyParameter)}. - * - * @param headerName - * the name of the header we wish to fetch + * + * @param headerName the name of the header we wish to fetch * @return the header as a {@link SipHeader} or null if not found * @throws SipParseException */ @@ -328,16 +309,15 @@ default boolean isTimeout() { /** * Convenience method for fetching the from-header - * + * * @return the from header as a buffer - * @throws SipParseException - * TODO + * @throws SipParseException TODO */ FromHeader getFromHeader() throws SipParseException; /** * Convenience method for fetching the to-header - * + * * @return the to header as a buffer */ ToHeader getToHeader() throws SipParseException; @@ -347,9 +327,9 @@ default boolean isTimeout() { * has been sent then there should always be a {@link ViaHeader} present. * However, you just created a {@link SipMessage} youself then this method * may return null so please check for it. - * + * * @return the top-most {@link ViaHeader} or null if there are no - * {@link ViaHeader}s on this message just yet. + * {@link ViaHeader}s on this message just yet. * @throws SipParseException */ ViaHeader getViaHeader() throws SipParseException; @@ -364,7 +344,6 @@ default boolean isTimeout() { List getViaHeaders() throws SipParseException; /** - * * @return * @throws SipParseException */ @@ -372,10 +351,10 @@ default boolean isTimeout() { /** * Get the top-most {@link RecordRouteHeader} header if present. - * + * * @return the top-most {@link RecordRouteHeader} header or null if there - * are no {@link RecordRouteHeader} headers found in this - * {@link SipMessage}. + * are no {@link RecordRouteHeader} headers found in this + * {@link SipMessage}. * @throws SipParseException */ RecordRouteHeader getRecordRouteHeader() throws SipParseException; @@ -384,7 +363,7 @@ default boolean isTimeout() { * Get all the RecordRoute-headers in this {@link SipMessage}. If there are * no {@link RecordRouteHeader}s in this {@link SipMessage} then an empty * list will be returned. - * + * * @return * @throws SipParseException */ @@ -392,9 +371,9 @@ default boolean isTimeout() { /** * Get the top-most {@link RouteHeader} header if present. - * + * * @return the top-most {@link RouteHeader} header or null if there are no - * {@link RouteHeader} headers found in this {@link SipMessage}. + * {@link RouteHeader} headers found in this {@link SipMessage}. * @throws SipParseException */ RouteHeader getRouteHeader() throws SipParseException; @@ -403,7 +382,7 @@ default boolean isTimeout() { * Get all the Route-headers in this {@link SipMessage}. If there are no * {@link RouteHeader}s in this {@link SipMessage} then an empty list will * be returned. - * + * * @return * @throws SipParseException */ @@ -411,7 +390,7 @@ default boolean isTimeout() { /** * Get the {@link ExpiresHeader} - * + * * @return * @throws SipParseException */ @@ -419,7 +398,7 @@ default boolean isTimeout() { /** * Get the {@link ContactHeader} - * + * * @return * @throws SipParseException */ @@ -428,7 +407,7 @@ default boolean isTimeout() { /** * Get the {@link ContentTypeHeader} for this message. If there is no * Content-Type header in this SIP message then null will be returned. - * + * * @return the {@link ContentTypeHeader} or null if there is none. * @throws SipParseException */ @@ -443,38 +422,38 @@ default boolean isTimeout() { * @return the value of the Content-Length header if present or zero if * there was no such header (or of course if the Content-Length header was * present but actually had the value of zero) - * * @throws SipParseException */ int getContentLength() throws SipParseException; /** * Convenience method for fetching the call-id-header - * + * * @return the call-id header as a buffer */ CallIdHeader getCallIDHeader() throws SipParseException; /** * Convenience method for fetching the CSeq header - * + * * @return * @throws SipParseException */ CSeqHeader getCSeqHeader() throws SipParseException; + WWWAuthenticateHeader getWWWAuthenticateHeader() throws SipParseException; + /** * Convenience method for determining whether the method of this message is * an INVITE or not. - * + *

    * Note, this method only determined if it is an INVITE, which then could * be either a request or a response, which you can check with {@link SipMessage#isRequest()} and - * {@link SipMessage#isResponse()} + * {@link SipMessage#isResponse()} * * @return true if the method of this message is a INVITE, false otherwise. - * @throws SipParseException - * in case the method could not be parsed out of the underlying - * buffer. + * @throws SipParseException in case the method could not be parsed out of the underlying + * buffer. */ default boolean isInvite() throws SipParseException { final Buffer m = getMethod(); @@ -491,7 +470,7 @@ default boolean isInvite() throws SipParseException { * * @return true if the method of this message is a REGISTER, false otherwise. * @throws SipParseException in case the method could not be parsed out of the underlying - * buffer. + * buffer. */ default boolean isRegister() throws SipParseException { final Buffer m = getMethod(); @@ -509,7 +488,7 @@ default boolean isRegister() throws SipParseException { * * @return true if the method of this message is a BYE, false otherwise. * @throws SipParseException in case the method could not be parsed out of the underlying - * buffer. + * buffer. */ default boolean isBye() throws SipParseException { final Buffer m = getMethod(); @@ -526,9 +505,8 @@ default boolean isBye() throws SipParseException { * is an ACK Request or not! * * @return true if the method of this message is a ACK, false otherwise. - * @throws SipParseException - * in case the method could not be parsed out of the underlying - * buffer. + * @throws SipParseException in case the method could not be parsed out of the underlying + * buffer. */ default boolean isAck() throws SipParseException { final Buffer m = getMethod(); @@ -544,9 +522,8 @@ default boolean isAck() throws SipParseException { * a CANCEL or not * * @return true if the method of this message is a CANCEL, false otherwise. - * @throws SipParseException - * in case the method could not be parsed out of the underlying - * buffer. + * @throws SipParseException in case the method could not be parsed out of the underlying + * buffer. */ default boolean isCancel() throws SipParseException { final Buffer m = getMethod(); @@ -564,9 +541,8 @@ default boolean isCancel() throws SipParseException { * this is an OPTIONS Request or not! * * @return true if the method of this message is a OPTIONS, false otherwise. - * @throws SipParseException - * in case the method could not be parsed out of the underlying - * buffer. + * @throws SipParseException in case the method could not be parsed out of the underlying + * buffer. */ default boolean isOptions() throws SipParseException { final Buffer m = getMethod(); @@ -585,9 +561,8 @@ default boolean isOptions() throws SipParseException { * this is an MESSAGE Request or not! * * @return true if the method of this message is a MESSAGE, false otherwise. - * @throws SipParseException - * in case the method could not be parsed out of the underlying - * buffer. + * @throws SipParseException in case the method could not be parsed out of the underlying + * buffer. */ default boolean isMessage() throws SipParseException { final Buffer m = getMethod(); @@ -605,9 +580,8 @@ default boolean isMessage() throws SipParseException { * is an INFO Request or not! * * @return true if the method of this message is a INFO, false otherwise. - * @throws SipParseException - * in case the method could not be parsed out of the underlying - * buffer. + * @throws SipParseException in case the method could not be parsed out of the underlying + * buffer. */ default boolean isInfo() throws SipParseException { final Buffer m = getMethod(); @@ -621,7 +595,7 @@ default boolean isInfo() throws SipParseException { /** * Checks whether or not this request is considered to be an "initial" * request, i.e., a request that does not go within a dialog. - * + * * @return * @throws SipParseException */ @@ -655,7 +629,7 @@ default boolean isSubsequent() throws SipParseException { *

  • proxy require - checks if all items of the proxy require header are * present in the list of the extensions from the module parameter * proxy_require.
  • - * + * *
  • parse uri's - checks if the specified URIs are present and parseable * by the SIP-router parsers
  • *
  • digest credentials - Check all instances of digest credentials in a @@ -663,13 +637,12 @@ default boolean isSubsequent() throws SipParseException { * and have meaningful values.
  • * *

    - * + * *

    * This list is taken from Kamailio.org *

    - * */ void verify(); @@ -682,7 +655,7 @@ default boolean isSubsequent() throws SipParseException { /** * Perform a deep clone of this SipMessage. - * + * * @return */ SipMessage clone(); @@ -691,7 +664,7 @@ default boolean isSubsequent() throws SipParseException { * Frame the supplied buffer into a {@link SipMessage}. No deep analysis of the message will be * performed so there is no guarantee that this {@link SipMessage} is actually a well formed * message. - * + * * @param buffer * @return the framed {@link SipMessage} */ @@ -701,7 +674,6 @@ static SipMessage frame(final Buffer buffer) throws SipParseException, IOExcepti } /** - * * @param buffer * @return * @throws IOException @@ -736,12 +708,12 @@ default List getAllHeaders() { * headers, request-uri, body etc through the withXXX-methods and then upon build time, * the builder will call out to any registered functions (as registered through the onXXX-methods) * allowing the application to make any last minute changes. - * + *

    * In a real SIP stack there are headers within a {@link SipMessage} that typically needs to be * manipulated before being sent out over the network. One such header is the {@link ViaHeader} where * the transport layer typically has the responsibility to fill out the ip and port from which the * message was sent as well as specify the transport used. - * + *

    * The idea is as follows: everything within this SIP library is immutable, which includes {@link SipRequest} * and {@link SipResponse}s but if you build a stack, the stack may actually need to change headers * before creating the "final" version of the {@link SipMessage} which is then sent out over the network. @@ -750,10 +722,10 @@ default List getAllHeaders() { * is about to be sent out and will (should) be filled out by the transport layer. Therefore, if you build * a stack you probably want to pass down a {@link Builder} to be "sent" all the way down to the transport layer * which will - * + *

    * All callback as registered through the various onXXXX-methods allow for multiple callbacks to be registered. * They are called in reverse order from the order of registration. - * + *

    * TODO: don't think I will do this anymore. If you add the same header via an of the three methods then * TODO: that header will be included three times. I.e., if you call withHeaderBuilder and withHeader and * TODO: the same header also exists in the template then it will be included that many times. @@ -763,14 +735,14 @@ default List getAllHeaders() { *

  • Any header added through a withXXX-method
  • *
  • Any header copied from a "template"
  • * - * + *

    * I.e., if you e.g. have added a from-header builder object * through {@link Builder#withFromHeader(AddressParametersHeader.Builder)} * method then that builder will be used even if this builder is based off * another {@link SipMessage} (which then serves as a "template") - * + *

    * TODO: add and/or clarify how headers are treated in general, i.e.: - * + *

    * A header can be added to the final {@link SipMessage} that is being built via 1 out of 3 ways: *

      *
    • Either via explicitly calling withXXXBuilder to add a builder object for that header
    • @@ -778,7 +750,7 @@ default List getAllHeaders() { *
    • Or by copying an existing header from the {@link SipMessage} we are using as a template * for constructing this new sip message.
    • *
    - * + *

    * No matter how a header is added to the final message (via one of the three ways described above) * you will be given the opportunity to change the value of the header by registering a function * for manipulating the header just before it gets added. You do so through two methods: @@ -786,13 +758,12 @@ default List getAllHeaders() { *

  • {@link io.pkts.packet.sip.SipMessage.Builder#onHeader(Function)}
  • *
  • or {@link io.pkts.packet.sip.SipMessage.Builder#onHeaderBuilder(Consumer)}
  • * - * + *

    * The first method is called when a header was added without a builder already created from it, * which is the case when a header is copied from the template or when you called onXXXHeader(header). * The reason for this is unless you actually want to change it, we don't want to waste time on * constructing a builder that you are not going to use. However, if you added the header * as a builder object then we will of coruse use that builder. - * */ interface Builder { @@ -826,7 +797,7 @@ default SipMessage.Builder toSipResponseBuilder() { *

  • {@link ContentLengthHeader} - Will be added if there is a body * on the message and the length set to the correct length.
  • * - * + *

    * but if you don't want that, simply call this method and all the defaults * of this builder will be suspended. Of course, if you wish to actually * construct a valid {@link SipMessage} you are then responsible for adding @@ -871,9 +842,9 @@ default SipMessage.Builder toSipResponseBuilder() { * returns a {@link SipHeader}, which is the header that will be pushed onto the new * {@link SipMessage}. If you do not want to include the header, then simply return * null and the header will be dropped. - * + *

    * If you wish to leave the header un-touched, then simply return it has is. - * + *

    * Also note that the following headers have explicit "on" methods (they are considered * to be "system" headers): * @@ -887,17 +858,16 @@ default SipMessage.Builder toSipResponseBuilder() { *

  • {@link MaxForwardsHeader}
  • *
  • {@link CSeqHeader}
  • * - * + *

    * The reason is simply because these are typically manipulated before * copying them over to a new request or response (e.g., Max Forwards is decremented, * CSeq may increase etc) and therefore it makes life easier if those headers are * "down casted" to their specific types. * - * * @param f * @return * @throws IllegalStateException in case a function already had been registered with - * this builder. + * this builder. */ Builder onHeader(Function f) throws IllegalStateException; @@ -905,13 +875,14 @@ default SipMessage.Builder toSipResponseBuilder() { * Adds the header to the list of headers already specified within this builder. * The header will be added last to the list of headers. Any already existing * headers with the same name will be preserved as is. - * + *

    * If there are any headers with the same name as part of the {@link SipMessage} * used as a template, then those headers will - * + *

    * TODO: this is essentially an "add header" so should it be called that? * TODO: and then should there be a set version? Just goes bad with a fluent * TODO: naming. + * * @param header * @return */ @@ -922,8 +893,9 @@ default SipMessage.Builder toSipResponseBuilder() { /** * Push the header to be the first on the list of existing headers already * added to this builder. - * + *

    * TODO: naming + * * @return */ Builder withPushHeader(SipHeader header); @@ -939,19 +911,25 @@ default SipMessage.Builder toSipResponseBuilder() { * @return */ Builder withFromHeader(FromHeader from); + Builder withFromHeader(String from); Builder onToHeader(Consumer> f); + Builder withToHeader(ToHeader to); + Builder withToHeader(String to); Builder onContactHeader(Consumer> f); + Builder withContactHeader(ContactHeader contact); Builder onCSeqHeader(Consumer f); + Builder withCSeqHeader(CSeqHeader cseq); Builder onMaxForwardsHeader(Consumer f); + Builder withMaxForwardsHeader(MaxForwardsHeader maxForwards); Builder withCallIdHeader(CallIdHeader callID); @@ -995,7 +973,7 @@ default SipMessage.Builder toSipResponseBuilder() { * @param routes * @return */ - Builder withRouteHeaders(RouteHeader ... routes); + Builder withRouteHeaders(RouteHeader... routes); Builder withRouteHeaders(List routes); @@ -1012,7 +990,7 @@ default SipMessage.Builder toSipResponseBuilder() { * Pop the top-most route. Note, if you e.g. add a {@link RouteHeader} via the method * {@link io.pkts.packet.sip.SipMessage.Builder#withTopMostRouteHeader(RouteHeader)} followed * by this method, then the route you just added will be removed again. Order is important! - * + *

    * Note: if you actually wanted to know the value of that {@link RouteHeader} then you should * really have checked it on the {@link SipMessage} you received and not on the builder * object. @@ -1074,7 +1052,7 @@ default SipMessage.Builder toSipResponseBuilder() { * @param recordRoute * @return */ - Builder withRecordRouteHeaders(RecordRouteHeader ... recordRoute); + Builder withRecordRouteHeaders(RecordRouteHeader... recordRoute); Builder withRecordRouteHeaders(List recordRoute); @@ -1088,7 +1066,6 @@ default SipMessage.Builder toSipResponseBuilder() { Builder withTopMostRecordRouteHeader(RecordRouteHeader recordRoute); /** - * * @param f * @return */ @@ -1116,15 +1093,15 @@ default SipMessage.Builder toSipResponseBuilder() { * index of the Via being processed. The top-most Via header will NEVER * be passed to this registered function but rather to the one explicitly * meant for processing the top-most via (see {@link Builder#onTopMostViaHeader(Consumer)}. - * + *

    * I.e., Let's say you have the following message (request or response, same same): * * - * ... - * Via: SIP/2.0/TCP 12.13.14.15;branch=z9hG4bK-asdf - * Via: SIP/2.0/UDP 60.61.62.63;branch=z9hG4bK-1234 - * Via: SIP/2.0/UDP 96.97.98.99;branch=z9hG4bK-wxyz - * ... + * ... + * Via: SIP/2.0/TCP 12.13.14.15;branch=z9hG4bK-asdf + * Via: SIP/2.0/UDP 60.61.62.63;branch=z9hG4bK-1234 + * Via: SIP/2.0/UDP 96.97.98.99;branch=z9hG4bK-wxyz + * ... * * *

      @@ -1166,7 +1143,7 @@ default SipMessage.Builder toSipResponseBuilder() { * @param vias * @return */ - Builder withViaHeaders(ViaHeader ... vias); + Builder withViaHeaders(ViaHeader... vias); /** * Set a list of Via headers. Any previously Via headers @@ -1194,7 +1171,7 @@ default SipMessage.Builder toSipResponseBuilder() { * a function with {@link Builder#onTopMostViaHeader(Consumer)} is rather silly * so therefore you can just indicate that you want a new top-most via header * but you will fill out all details later. - * + *

      * NOTE: if you do NOT register a function to handle this "empty" Via-header * through the method {@link Builder#onTopMostViaHeader(Consumer)} things will * blow up later with you try to build this message. @@ -1207,7 +1184,7 @@ default SipMessage.Builder toSipResponseBuilder() { * Pop the top-most via. Note, if you e.g. add a {@link ViaHeader} through the method * {@link io.pkts.packet.sip.SipMessage.Builder#withTopMostViaHeader(ViaHeader)} followed * by this method, then the Via you just added will be removed again. Order is important! - * + *

      * Note: if you actually wanted to know the value of that {@link ViaHeader} then you should * really have checked it on the {@link SipMessage} you received and not on the builder * object. diff --git a/pkts-sip/src/main/java/io/pkts/packet/sip/header/AddressParametersHeader.java b/pkts-sip/src/main/java/io/pkts/packet/sip/header/AddressParametersHeader.java index ce0b907e..76a4781e 100644 --- a/pkts-sip/src/main/java/io/pkts/packet/sip/header/AddressParametersHeader.java +++ b/pkts-sip/src/main/java/io/pkts/packet/sip/header/AddressParametersHeader.java @@ -72,6 +72,7 @@ default boolean isAddressParametersHeader() { return true; } + @Override default AddressParametersHeader toAddressParametersHeader() throws ClassCastException { return this; } @@ -100,6 +101,7 @@ protected Builder(final Buffer name, final ParametersSupport params) { } + @Override public final Builder withValue(final Buffer buffer) { throw new RuntimeException("Not implemented yet"); // Address.frame(buffer).copy(); @@ -388,6 +390,7 @@ public final Builder withAddress(final Address address) throws SipParseExcept * @throws SipParseException in case anything goes wrong while constructing the * {@link ToHeader}. */ + @Override public final T build() throws SipParseException { if (addressBuilder == null) { throw new SipParseException("You must specify an address of some sort."); diff --git a/pkts-sip/src/main/java/io/pkts/packet/sip/header/CSeqHeader.java b/pkts-sip/src/main/java/io/pkts/packet/sip/header/CSeqHeader.java index d4cf0867..29c57ef2 100644 --- a/pkts-sip/src/main/java/io/pkts/packet/sip/header/CSeqHeader.java +++ b/pkts-sip/src/main/java/io/pkts/packet/sip/header/CSeqHeader.java @@ -114,6 +114,7 @@ public Builder withValue(Buffer value) { throw new RuntimeException("Not implemented yet"); } + @Override public CSeqHeader build() { final int size = Buffers.stringSizeOf(this.cseq); final Buffer value = Buffers.createBuffer(size + 1 + this.method.getReadableBytes()); diff --git a/pkts-sip/src/main/java/io/pkts/packet/sip/header/SipHeader.java b/pkts-sip/src/main/java/io/pkts/packet/sip/header/SipHeader.java index 64dea7ca..46d7e201 100644 --- a/pkts-sip/src/main/java/io/pkts/packet/sip/header/SipHeader.java +++ b/pkts-sip/src/main/java/io/pkts/packet/sip/header/SipHeader.java @@ -128,7 +128,6 @@ default int getBufferSize() { * Note, the header returned really is a {@link SipHeader} and is NOT e.g. a {@link ToHeader}. * If you really need to parse it as a {@link ToHeader} you should use the * - * @param header the raw header * @return a new {@link SipHeader}. * @throws SipParseException in case the header is not a correct formatted header. */ @@ -188,9 +187,9 @@ default boolean isFromHeader() { try { if (m.getReadableBytes() == 4) { return (m.getByte(0) == 'F' || m.getByte(0) == 'f') && - (m.getByte(1) == 'r' || m.getByte(1) == 'R') && - (m.getByte(2) == 'o' || m.getByte(2) == 'O') && - (m.getByte(3) == 'm' || m.getByte(3) == 'M'); + (m.getByte(1) == 'r' || m.getByte(1) == 'R') && + (m.getByte(2) == 'o' || m.getByte(2) == 'O') && + (m.getByte(3) == 'm' || m.getByte(3) == 'M'); } else if (m.getReadableBytes() == 1) { return m.getByte(0) == 'F' || m.getByte(0) == 'f'; } @@ -200,6 +199,39 @@ default boolean isFromHeader() { return false; } + default boolean isWWWAuthenticateHeader() { + //WWW-Authenticate + final Buffer m = getName(); + try { + if (m.getReadableBytes() == 16) { + return (m.getByte(0) == 'W' || m.getByte(0) == 'w') && + (m.getByte(1) == 'W' || m.getByte(1) == 'w') && + (m.getByte(2) == 'W' || m.getByte(2) == 'w') && + m.getByte(3) == '-' && + (m.getByte(4) == 'A' || m.getByte(4) == 'a') && + (m.getByte(5) == 'U' || m.getByte(5) == 'u') && + (m.getByte(6) == 'T' || m.getByte(6) == 't') && + (m.getByte(7) == 'H' || m.getByte(7) == 'h') && + (m.getByte(8) == 'E' || m.getByte(8) == 'e') && + (m.getByte(9) == 'N' || m.getByte(9) == 'n') && + (m.getByte(10) == 'T' || m.getByte(10) == 't') && + (m.getByte(11) == 'I' || m.getByte(11) == 'i') && + (m.getByte(12) == 'C' || m.getByte(12) == 'c') && + (m.getByte(13) == 'A' || m.getByte(13) == 'a') && + (m.getByte(14) == 'T' || m.getByte(14) == 't') && + (m.getByte(15) == 'E' || m.getByte(15) == 'e'); + } + } catch (final IOException e) { + throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_HEADER_NAME_DUE_TO_UNDERLYING_IO_EXCEPTION, e); + } + return false; + } + + default WWWAuthenticateHeader toWWWAuthenticateHeader() { + throw new ClassCastException(CANNOT_CAST_HEADER_OF_TYPE + getClass().getName() + + " to type " + WWWAuthenticateHeader.class.getName()); + } + default FromHeader toFromHeader() { throw new ClassCastException(CANNOT_CAST_HEADER_OF_TYPE + getClass().getName() + " to type " + FromHeader.class.getName()); @@ -244,12 +276,12 @@ default boolean isContactHeader() { try { if (m.getReadableBytes() == 7) { return (m.getByte(0) == 'C' || m.getByte(0) == 'c') && - (m.getByte(1) == 'o' || m.getByte(1) == 'O') && - (m.getByte(2) == 'n' || m.getByte(2) == 'N') && - (m.getByte(3) == 't' || m.getByte(3) == 'T') && - (m.getByte(4) == 'a' || m.getByte(4) == 'A') && - (m.getByte(5) == 'c' || m.getByte(5) == 'C') && - (m.getByte(6) == 't' || m.getByte(6) == 'T'); + (m.getByte(1) == 'o' || m.getByte(1) == 'O') && + (m.getByte(2) == 'n' || m.getByte(2) == 'N') && + (m.getByte(3) == 't' || m.getByte(3) == 'T') && + (m.getByte(4) == 'a' || m.getByte(4) == 'A') && + (m.getByte(5) == 'c' || m.getByte(5) == 'C') && + (m.getByte(6) == 't' || m.getByte(6) == 'T'); } else if (m.getReadableBytes() == 1) { // short form for the contact header is 'm' return (m.getByte(0) == 'M' || m.getByte(0) == 'm'); @@ -270,12 +302,12 @@ default boolean isSubjectHeader() { try { if (m.getReadableBytes() == 7) { return (m.getByte(0) == 'S' || m.getByte(0) == 's') && - (m.getByte(1) == 'u' || m.getByte(1) == 'U') && - (m.getByte(2) == 'b' || m.getByte(2) == 'B') && - (m.getByte(3) == 'j' || m.getByte(3) == 'J') && - (m.getByte(4) == 'e' || m.getByte(4) == 'E') && - (m.getByte(5) == 'c' || m.getByte(5) == 'C') && - (m.getByte(6) == 't' || m.getByte(6) == 'T'); + (m.getByte(1) == 'u' || m.getByte(1) == 'U') && + (m.getByte(2) == 'b' || m.getByte(2) == 'B') && + (m.getByte(3) == 'j' || m.getByte(3) == 'J') && + (m.getByte(4) == 'e' || m.getByte(4) == 'E') && + (m.getByte(5) == 'c' || m.getByte(5) == 'C') && + (m.getByte(6) == 't' || m.getByte(6) == 'T'); } } catch (final IOException e) { throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_HEADER_NAME_DUE_TO_UNDERLYING_IO_EXCEPTION, e); @@ -288,12 +320,12 @@ default boolean isCallIdHeader() { try { if (m.getReadableBytes() == 7) { return (m.getByte(0) == 'C' || m.getByte(0) == 'c') && - (m.getByte(1) == 'a' || m.getByte(1) == 'A') && - (m.getByte(2) == 'l' || m.getByte(2) == 'L') && - (m.getByte(3) == 'l' || m.getByte(3) == 'L') && + (m.getByte(1) == 'a' || m.getByte(1) == 'A') && + (m.getByte(2) == 'l' || m.getByte(2) == 'L') && + (m.getByte(3) == 'l' || m.getByte(3) == 'L') && m.getByte(4) == '-' && - (m.getByte(5) == 'I' || m.getByte(5) == 'i') && - (m.getByte(6) == 'D' || m.getByte(6) == 'd'); + (m.getByte(5) == 'I' || m.getByte(5) == 'i') && + (m.getByte(6) == 'D' || m.getByte(6) == 'd'); } else if (m.getReadableBytes() == 1) { // short form for the call-id header is 'i' return (m.getByte(0) == 'I' || m.getByte(0) == 'i'); @@ -315,10 +347,10 @@ default boolean isRouteHeader() { if (m.getReadableBytes() == 5) { try { return (m.getByte(0) == 'R' || m.getByte(0) == 'r') && - (m.getByte(1) == 'o' || m.getByte(1) == 'O') && - (m.getByte(2) == 'u' || m.getByte(2) == 'U') && - (m.getByte(3) == 't' || m.getByte(3) == 'T') && - (m.getByte(4) == 'e' || m.getByte(4) == 'E'); + (m.getByte(1) == 'o' || m.getByte(1) == 'O') && + (m.getByte(2) == 'u' || m.getByte(2) == 'U') && + (m.getByte(3) == 't' || m.getByte(3) == 'T') && + (m.getByte(4) == 'e' || m.getByte(4) == 'E'); } catch (final IOException e) { throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_HEADER_NAME_DUE_TO_UNDERLYING_IO_EXCEPTION, e); } @@ -331,17 +363,17 @@ default boolean isRecordRouteHeader() { if (m.getReadableBytes() == 12) { try { return (m.getByte(0) == 'R' || m.getByte(0) == 'r') && - (m.getByte(1) == 'e' || m.getByte(1) == 'E') && - (m.getByte(2) == 'c' || m.getByte(2) == 'C') && - (m.getByte(3) == 'o' || m.getByte(3) == 'O') && - (m.getByte(4) == 'r' || m.getByte(4) == 'R') && - (m.getByte(5) == 'd' || m.getByte(5) == 'D') && + (m.getByte(1) == 'e' || m.getByte(1) == 'E') && + (m.getByte(2) == 'c' || m.getByte(2) == 'C') && + (m.getByte(3) == 'o' || m.getByte(3) == 'O') && + (m.getByte(4) == 'r' || m.getByte(4) == 'R') && + (m.getByte(5) == 'd' || m.getByte(5) == 'D') && m.getByte(6) == '-' && - (m.getByte(7) == 'R' || m.getByte(7) == 'r') && - (m.getByte(8) == 'o' || m.getByte(8) == 'O') && - (m.getByte(9) == 'u' || m.getByte(9) == 'U') && - (m.getByte(10) == 't' || m.getByte(10) == 'T') && - (m.getByte(11) == 'e' || m.getByte(11) == 'E'); + (m.getByte(7) == 'R' || m.getByte(7) == 'r') && + (m.getByte(8) == 'o' || m.getByte(8) == 'O') && + (m.getByte(9) == 'u' || m.getByte(9) == 'U') && + (m.getByte(10) == 't' || m.getByte(10) == 'T') && + (m.getByte(11) == 'e' || m.getByte(11) == 'E'); } catch (final IOException e) { throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_HEADER_NAME_DUE_TO_UNDERLYING_IO_EXCEPTION, e); } @@ -364,19 +396,19 @@ default boolean isContentLengthHeader() { try { if (m.getReadableBytes() == 14) { return (m.getByte(0) == 'C' || m.getByte(0) == 'c') && - (m.getByte(1) == 'o' || m.getByte(1) == 'O') && - (m.getByte(2) == 'n' || m.getByte(2) == 'N') && - (m.getByte(3) == 't' || m.getByte(3) == 'T') && - (m.getByte(4) == 'e' || m.getByte(4) == 'E') && - (m.getByte(5) == 'n' || m.getByte(5) == 'N') && - (m.getByte(6) == 't' || m.getByte(6) == 'T') && + (m.getByte(1) == 'o' || m.getByte(1) == 'O') && + (m.getByte(2) == 'n' || m.getByte(2) == 'N') && + (m.getByte(3) == 't' || m.getByte(3) == 'T') && + (m.getByte(4) == 'e' || m.getByte(4) == 'E') && + (m.getByte(5) == 'n' || m.getByte(5) == 'N') && + (m.getByte(6) == 't' || m.getByte(6) == 'T') && m.getByte(7) == '-' && - (m.getByte(8) == 'L' || m.getByte(8) == 'l') && - (m.getByte(9) == 'e' || m.getByte(9) == 'E') && - (m.getByte(10) == 'n' || m.getByte(10) == 'N') && - (m.getByte(11) == 'g' || m.getByte(11) == 'G') && - (m.getByte(12) == 't' || m.getByte(12) == 'T') && - (m.getByte(13) == 'h' || m.getByte(13) == 'H'); + (m.getByte(8) == 'L' || m.getByte(8) == 'l') && + (m.getByte(9) == 'e' || m.getByte(9) == 'E') && + (m.getByte(10) == 'n' || m.getByte(10) == 'N') && + (m.getByte(11) == 'g' || m.getByte(11) == 'G') && + (m.getByte(12) == 't' || m.getByte(12) == 'T') && + (m.getByte(13) == 'h' || m.getByte(13) == 'H'); } else if (m.getReadableBytes() == 1) { return (m.getByte(0) == 'L' || m.getByte(0) == 'l'); @@ -397,17 +429,17 @@ default boolean isContentTypeHeader() { try { if (m.getReadableBytes() == 12) { return (m.getByte(0) == 'C' || m.getByte(0) == 'c') && - (m.getByte(1) == 'o' || m.getByte(1) == 'O') && - (m.getByte(2) == 'n' || m.getByte(2) == 'N') && - (m.getByte(3) == 't' || m.getByte(3) == 'T') && - (m.getByte(4) == 'e' || m.getByte(4) == 'E') && - (m.getByte(5) == 'n' || m.getByte(5) == 'N') && - (m.getByte(6) == 't' || m.getByte(6) == 'T') && + (m.getByte(1) == 'o' || m.getByte(1) == 'O') && + (m.getByte(2) == 'n' || m.getByte(2) == 'N') && + (m.getByte(3) == 't' || m.getByte(3) == 'T') && + (m.getByte(4) == 'e' || m.getByte(4) == 'E') && + (m.getByte(5) == 'n' || m.getByte(5) == 'N') && + (m.getByte(6) == 't' || m.getByte(6) == 'T') && m.getByte(7) == '-' && - (m.getByte(8) == 'T' || m.getByte(8) == 't') && - (m.getByte(9) == 'y' || m.getByte(9) == 'Y') && - (m.getByte(10) == 'p' || m.getByte(10) == 'P') && - (m.getByte(11) == 'e' || m.getByte(11) == 'E'); + (m.getByte(8) == 'T' || m.getByte(8) == 't') && + (m.getByte(9) == 'y' || m.getByte(9) == 'Y') && + (m.getByte(10) == 'p' || m.getByte(10) == 'P') && + (m.getByte(11) == 'e' || m.getByte(11) == 'E'); } else if (m.getReadableBytes() == 1) { return (m.getByte(0) == 'C' || m.getByte(0) == 'c'); } @@ -427,12 +459,12 @@ default boolean isExpiresHeader() { try { if (m.getReadableBytes() == 7) { return (m.getByte(0) == 'E' || m.getByte(0) == 'e') && - (m.getByte(1) == 'x' || m.getByte(1) == 'X') && - (m.getByte(2) == 'p' || m.getByte(2) == 'P') && - (m.getByte(3) == 'i' || m.getByte(3) == 'I') && - (m.getByte(4) == 'r' || m.getByte(4) == 'R') && - (m.getByte(5) == 'e' || m.getByte(5) == 'E') && - (m.getByte(6) == 's' || m.getByte(6) == 'S'); + (m.getByte(1) == 'x' || m.getByte(1) == 'X') && + (m.getByte(2) == 'p' || m.getByte(2) == 'P') && + (m.getByte(3) == 'i' || m.getByte(3) == 'I') && + (m.getByte(4) == 'r' || m.getByte(4) == 'R') && + (m.getByte(5) == 'e' || m.getByte(5) == 'E') && + (m.getByte(6) == 's' || m.getByte(6) == 'S'); } } catch (final IOException e) { throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_HEADER_NAME_DUE_TO_UNDERLYING_IO_EXCEPTION, e); @@ -450,9 +482,9 @@ default boolean isCSeqHeader() { try { if (m.getReadableBytes() == 4) { return (m.getByte(0) == 'C' || m.getByte(0) == 'c') && - (m.getByte(1) == 'S' || m.getByte(1) == 's') && - (m.getByte(2) == 'e' || m.getByte(2) == 'E') && - (m.getByte(3) == 'q' || m.getByte(3) == 'Q'); + (m.getByte(1) == 'S' || m.getByte(1) == 's') && + (m.getByte(2) == 'e' || m.getByte(2) == 'E') && + (m.getByte(3) == 'q' || m.getByte(3) == 'Q'); } } catch (final IOException e) { throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_HEADER_NAME_DUE_TO_UNDERLYING_IO_EXCEPTION, e); @@ -470,17 +502,17 @@ default boolean isMaxForwardsHeader() { if (m.getReadableBytes() == 12) { try { return (m.getByte(0) == 'M' || m.getByte(0) == 'm') && - (m.getByte(1) == 'a' || m.getByte(1) == 'A') && - (m.getByte(2) == 'x' || m.getByte(2) == 'X') && + (m.getByte(1) == 'a' || m.getByte(1) == 'A') && + (m.getByte(2) == 'x' || m.getByte(2) == 'X') && m.getByte(3) == '-' && - (m.getByte(4) == 'F' || m.getByte(4) == 'f') && - (m.getByte(5) == 'o' || m.getByte(5) == 'O') && - (m.getByte(6) == 'r' || m.getByte(6) == 'R') && - (m.getByte(7) == 'w' || m.getByte(7) == 'W') && - (m.getByte(8) == 'a' || m.getByte(8) == 'A') && - (m.getByte(9) == 'r' || m.getByte(9) == 'R') && - (m.getByte(10) == 'd' || m.getByte(10) == 'D') && - (m.getByte(11) == 's' || m.getByte(11) == 'S'); + (m.getByte(4) == 'F' || m.getByte(4) == 'f') && + (m.getByte(5) == 'o' || m.getByte(5) == 'O') && + (m.getByte(6) == 'r' || m.getByte(6) == 'R') && + (m.getByte(7) == 'w' || m.getByte(7) == 'W') && + (m.getByte(8) == 'a' || m.getByte(8) == 'A') && + (m.getByte(9) == 'r' || m.getByte(9) == 'R') && + (m.getByte(10) == 'd' || m.getByte(10) == 'D') && + (m.getByte(11) == 's' || m.getByte(11) == 'S'); } catch (final IOException e) { throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_HEADER_NAME_DUE_TO_UNDERLYING_IO_EXCEPTION, e); } @@ -498,8 +530,8 @@ default boolean isViaHeader() { try { if (m.getReadableBytes() == 3) { return (m.getByte(0) == 'V' || m.getByte(0) == 'v') && - (m.getByte(1) == 'i' || m.getByte(1) == 'I') && - (m.getByte(2) == 'a' || m.getByte(2) == 'A'); + (m.getByte(1) == 'i' || m.getByte(1) == 'I') && + (m.getByte(2) == 'a' || m.getByte(2) == 'A'); } else if (m.getReadableBytes() == 1) { // Short form for the via header is a 'v' return (m.getByte(0) == 'V' || m.getByte(0) == 'v'); diff --git a/pkts-sip/src/main/java/io/pkts/packet/sip/header/WWWAuthenticateHeader.java b/pkts-sip/src/main/java/io/pkts/packet/sip/header/WWWAuthenticateHeader.java new file mode 100644 index 00000000..ab5d4f9f --- /dev/null +++ b/pkts-sip/src/main/java/io/pkts/packet/sip/header/WWWAuthenticateHeader.java @@ -0,0 +1,92 @@ +package io.pkts.packet.sip.header; + +import io.pkts.buffer.Buffer; +import io.pkts.buffer.Buffers; +import io.pkts.packet.sip.SipParseException; +import io.pkts.packet.sip.header.impl.WWWAuthenticateHeaderImpl; + + +public interface WWWAuthenticateHeader extends SipHeader { + + Buffer NAME = Buffers.wrap("WWW-Authenticate"); + + Buffer getRealm(); + + Buffer getNonce(); + + Buffer getAlgorithm(); + + Buffer getQop(); + + static WWWAuthenticateHeader frame(final Buffer buffer) throws SipParseException { + try { + return new WWWAuthenticateHeader.Builder(buffer).build(); + } catch (final Exception e) { + throw new SipParseException(0, "Unable to frame the WWWAuthenticate header due to IOException", e); + } + } + + @Override + default WWWAuthenticateHeader toWWWAuthenticateHeader() { + return this; + } + + + class Builder implements SipHeader.Builder { + private Buffer value; + + private Buffer realm; + private Buffer nonce; + private Buffer algorithm; + private Buffer qop; + + public Builder() { + + } + + public Builder(Buffer value) { + this.value = value; + } + + @Override + public WWWAuthenticateHeader.Builder withValue(Buffer value) { + this.value = value; + return this; + } + + public WWWAuthenticateHeader.Builder withRealm(Buffer realm) { + this.realm = realm; + return this; + } + + public WWWAuthenticateHeader.Builder withNonce(Buffer nonce) { + this.nonce = nonce; + return this; + } + + public WWWAuthenticateHeader.Builder withAlgorithm(Buffer algorithm) { + this.algorithm = algorithm; + return this; + } + + public WWWAuthenticateHeader.Builder withQop(Buffer qop) { + this.qop = qop; + return this; + } + + @Override + public WWWAuthenticateHeader build() throws SipParseException { + if (value == null && + (this.realm == null && this.nonce == null)) { + throw new SipParseException("You must specify the [value] or [realm/nonce] of the WWWAuthenticate-Header"); + } + + if (this.value != null) { + return new WWWAuthenticateHeaderImpl(value); + } else { + return new WWWAuthenticateHeaderImpl(realm, nonce, algorithm, qop); + } + } + } + +} diff --git a/pkts-sip/src/main/java/io/pkts/packet/sip/header/impl/AddressParametersHeaderImpl.java b/pkts-sip/src/main/java/io/pkts/packet/sip/header/impl/AddressParametersHeaderImpl.java index 3ba71c8b..a5bfc5e0 100644 --- a/pkts-sip/src/main/java/io/pkts/packet/sip/header/impl/AddressParametersHeaderImpl.java +++ b/pkts-sip/src/main/java/io/pkts/packet/sip/header/impl/AddressParametersHeaderImpl.java @@ -41,6 +41,7 @@ public Address getAddress() { return this.address; } + @Override public AddressParametersHeader.Builder copy() { AddressParametersHeader.Builder b = AddressParametersHeader.with(getName()); b.withParameters(getRawParams()); diff --git a/pkts-sip/src/main/java/io/pkts/packet/sip/header/impl/WWWAuthenticateHeaderImpl.java b/pkts-sip/src/main/java/io/pkts/packet/sip/header/impl/WWWAuthenticateHeaderImpl.java new file mode 100644 index 00000000..42413ead --- /dev/null +++ b/pkts-sip/src/main/java/io/pkts/packet/sip/header/impl/WWWAuthenticateHeaderImpl.java @@ -0,0 +1,184 @@ +package io.pkts.packet.sip.header.impl; + +import io.pkts.buffer.Buffer; +import io.pkts.buffer.Buffers; +import io.pkts.packet.sip.SipParseException; +import io.pkts.packet.sip.header.WWWAuthenticateHeader; +import io.pkts.packet.sip.impl.SipParser; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class WWWAuthenticateHeaderImpl extends SipHeaderImpl implements WWWAuthenticateHeader { + + + private Map paramMap = new LinkedHashMap<>(); + + private Buffer realm; + private Buffer nonce; + private Buffer algorithm; + private Buffer qop; + + + /** + * @param value + */ + public WWWAuthenticateHeaderImpl(Buffer value) { + super(WWWAuthenticateHeader.NAME, value); + + Buffer original = value.clone(); + Buffer params = null; + if (original.hasReadableBytes()) { + params = original.slice("Digest ".length(), original.getUpperBoundary()); + } + + final byte[] VALUE_END_1 = Buffers.wrap("\", ").getArray(); + final byte[] VALUE_END_2 = Buffers.wrap(", ").getArray(); + + //WWW-Authenticate: Digest realm="10.32.26.25", + // nonce="bee3366b-cf59-476e-bc5e-334e0d65b386", + // algorithm=MD5, + // qop="auth" + + try { + // 思路: + // 1 遇到[=]号是key结束,遇到[,]或[", ]或[\r\n]是value结束 + // 2 每次遇"="或”,”标识lastMarkIndex + int lastMarkIndex = params.getReaderIndex(); + boolean inKey = true; + Buffer latestKey = Buffers.EMPTY_BUFFER, latestValue; + while (params.hasReadableBytes() && params.getReaderIndex() <= params.getUpperBoundary()) { + if (inKey && SipParser.isNext(params, SipParser.EQ)) { + //遇到[=]认为key结束 + latestKey = params.slice(lastMarkIndex, params.getReaderIndex()); + params.setReaderIndex(params.getReaderIndex() + 1); + if (SipParser.isNext(params, SipParser.DQUOT)) { + //跳过[="]等号后的第1个双引号 + params.setReaderIndex(params.getReaderIndex() + 1); + inKey = false; + } + lastMarkIndex = params.getReaderIndex(); + } else if (params.getReadableBytes() == 1 || + SipParser.isNext(params, VALUE_END_1) || + SipParser.isNext(params, VALUE_END_2)) { + //遇到[", ]或[, ]视为value结束 + if (params.getReadableBytes() == 1 && params.peekByte() != SipParser.DQUOT) { + latestValue = params.slice(lastMarkIndex, params.getReaderIndex() + 1); + } else { + latestValue = params.slice(lastMarkIndex, params.getReaderIndex()); + } + + paramMap.put(latestKey, latestValue); + + if (params.getReadableBytes() == 1) { + params.setReaderIndex(params.getReaderIndex() + 1); + } else if (SipParser.isNext(params, VALUE_END_1)) { + params.setReaderIndex(params.getReaderIndex() + VALUE_END_1.length); + } else if (SipParser.isNext(params, VALUE_END_2)) { + params.setReaderIndex(params.getReaderIndex() + VALUE_END_2.length); + } + + lastMarkIndex = params.getReaderIndex(); + + inKey = true; + } else { + params.setReaderIndex(params.getReaderIndex() + 1); + } + } + } catch (Exception e) { + throw new SipParseException(NAME + " parse error, " + e.getCause()); + } + } + + + public WWWAuthenticateHeaderImpl(Buffer realm, Buffer nonce, Buffer algorithm, Buffer qop) { + super(WWWAuthenticateHeader.NAME, Buffers.EMPTY_BUFFER); + this.realm = realm; + this.nonce = nonce; + this.algorithm = algorithm; + this.qop = qop; + } + + @Override + public Buffer getValue() { + Buffer value = super.getValue(); + if (value != null && value != Buffers.EMPTY_BUFFER) { + return value; + } + StringBuilder sb = new StringBuilder("Digest realm=\"" + this.getRealm() + "\", nonce=\"" + this.getNonce() + "\""); + if (this.getAlgorithm() != null) { + sb.append(", algorithm=" + this.getAlgorithm()); + } + if (this.getQop() != null) { + sb.append(", qop=\"" + this.getQop() + "\""); + } + value = Buffers.wrap(sb.toString()); + return value; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(NAME.toString()); + sb.append(": Digest realm=\"" + this.getRealm() + "\", nonce=\"" + this.getNonce() + "\""); + if (this.getAlgorithm() != null) { + sb.append(", algorithm=" + this.getAlgorithm()); + } + if (this.getQop() != null) { + sb.append(", qop=\"" + this.getQop() + "\""); + } + return sb.toString(); + } + + + @Override + public WWWAuthenticateHeader.Builder copy() { + return new WWWAuthenticateHeader.Builder(getValue()); + } + + @Override + public WWWAuthenticateHeader ensure() { + return this; + } + + @Override + public WWWAuthenticateHeader clone() { + final Buffer value = getValue(); + return new WWWAuthenticateHeaderImpl(value.clone()); + } + + @Override + public Buffer getRealm() { + if (realm != null) { + return realm; + } + realm = paramMap.get(Buffers.wrap("realm")); + return realm; + } + + @Override + public Buffer getNonce() { + if (nonce != null) { + return nonce; + } + nonce = paramMap.get(Buffers.wrap("nonce")); + return nonce; + } + + @Override + public Buffer getAlgorithm() { + if (algorithm != null) { + return algorithm; + } + algorithm = paramMap.get(Buffers.wrap("algorithm")); + return algorithm; + } + + @Override + public Buffer getQop() { + if (qop != null) { + return qop; + } + qop = paramMap.get(Buffers.wrap("qop")); + return qop; + } +} diff --git a/pkts-sip/src/main/java/io/pkts/packet/sip/impl/ImmutableSipMessage.java b/pkts-sip/src/main/java/io/pkts/packet/sip/impl/ImmutableSipMessage.java index fb0ee581..04fc7a60 100644 --- a/pkts-sip/src/main/java/io/pkts/packet/sip/impl/ImmutableSipMessage.java +++ b/pkts-sip/src/main/java/io/pkts/packet/sip/impl/ImmutableSipMessage.java @@ -3,19 +3,7 @@ import io.pkts.buffer.Buffer; import io.pkts.packet.sip.SipMessage; import io.pkts.packet.sip.SipParseException; -import io.pkts.packet.sip.header.CSeqHeader; -import io.pkts.packet.sip.header.CallIdHeader; -import io.pkts.packet.sip.header.ContactHeader; -import io.pkts.packet.sip.header.ContentLengthHeader; -import io.pkts.packet.sip.header.ContentTypeHeader; -import io.pkts.packet.sip.header.ExpiresHeader; -import io.pkts.packet.sip.header.FromHeader; -import io.pkts.packet.sip.header.MaxForwardsHeader; -import io.pkts.packet.sip.header.RecordRouteHeader; -import io.pkts.packet.sip.header.RouteHeader; -import io.pkts.packet.sip.header.SipHeader; -import io.pkts.packet.sip.header.ToHeader; -import io.pkts.packet.sip.header.ViaHeader; +import io.pkts.packet.sip.header.*; import java.util.ArrayList; import java.util.Collections; @@ -239,6 +227,12 @@ public ExpiresHeader getExpiresHeader() throws SipParseException { return header != null ? header.ensure().toExpiresHeader() : null; } + @Override + public WWWAuthenticateHeader getWWWAuthenticateHeader() throws SipParseException{ + final SipHeader header = findHeader(WWWAuthenticateHeader.NAME.toString()); + return header != null ? header.ensure().toWWWAuthenticateHeader() : null; + } + @Override public ContactHeader getContactHeader() throws SipParseException { return contactHeader != null ? contactHeader.ensure().toContactHeader() : null; diff --git a/pkts-sip/src/main/java/io/pkts/packet/sip/impl/SipMessageBuilder.java b/pkts-sip/src/main/java/io/pkts/packet/sip/impl/SipMessageBuilder.java index 8ca005bd..c58be627 100644 --- a/pkts-sip/src/main/java/io/pkts/packet/sip/impl/SipMessageBuilder.java +++ b/pkts-sip/src/main/java/io/pkts/packet/sip/impl/SipMessageBuilder.java @@ -18,11 +18,7 @@ import io.pkts.packet.sip.header.ToHeader; import io.pkts.packet.sip.header.ViaHeader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @@ -594,7 +590,7 @@ public T build() { int msgSize = 2; final int headerCount = this.headers.size() + sizeOf(viaHeaders) + sizeOf(recordRouteHeaders) + sizeOf(routeHeaders); - final Map> finalHeaders = new HashMap<>(headerCount); + final Map> finalHeaders = new LinkedHashMap<>(headerCount); SipHeader contentLengthHeader = null; @@ -612,6 +608,19 @@ public T build() { routeHeader = null; recordRouteHeader = null; contactHeader = null; + + if (this.viaHeaders != null) { + for (int j = 0; j < this.viaHeaders.size(); ++j) { + final ViaHeader finalVia = processVia(j, this.viaHeaders.get(j)); + msgSize += finalVia.getBufferSize() + 2; + if (viaHeader == null) { + viaHeader = finalVia; + } + + finalHeaders.computeIfAbsent(finalVia.getName().toString(), k -> new ArrayList<>()).add(finalVia); + } + } + for (int i = 0; i < this.headers.size(); ++i) { final SipHeader header = this.headers.get(i); if (header != null) { @@ -630,18 +639,6 @@ public T build() { } } - if (this.viaHeaders != null) { - for (int j = 0; j < this.viaHeaders.size(); ++j) { - final ViaHeader finalVia = processVia(j, this.viaHeaders.get(j)); - msgSize += finalVia.getBufferSize() + 2; - if (viaHeader == null) { - viaHeader = finalVia; - } - - finalHeaders.computeIfAbsent(finalVia.getName().toString(), k -> new ArrayList<>()).add(finalVia); - } - } - if (this.recordRouteHeaders != null) { for (int j = 0; j < this.recordRouteHeaders.size(); ++j) { final Consumer> f = diff --git a/pkts-sip/src/main/java/io/pkts/packet/sip/impl/SipParser.java b/pkts-sip/src/main/java/io/pkts/packet/sip/impl/SipParser.java index ad5b2903..334a5e00 100644 --- a/pkts-sip/src/main/java/io/pkts/packet/sip/impl/SipParser.java +++ b/pkts-sip/src/main/java/io/pkts/packet/sip/impl/SipParser.java @@ -7,19 +7,7 @@ import io.pkts.buffer.Buffers; import io.pkts.packet.sip.SipMessage; import io.pkts.packet.sip.SipParseException; -import io.pkts.packet.sip.header.CSeqHeader; -import io.pkts.packet.sip.header.CallIdHeader; -import io.pkts.packet.sip.header.ContactHeader; -import io.pkts.packet.sip.header.ContentLengthHeader; -import io.pkts.packet.sip.header.ContentTypeHeader; -import io.pkts.packet.sip.header.ExpiresHeader; -import io.pkts.packet.sip.header.FromHeader; -import io.pkts.packet.sip.header.MaxForwardsHeader; -import io.pkts.packet.sip.header.RecordRouteHeader; -import io.pkts.packet.sip.header.RouteHeader; -import io.pkts.packet.sip.header.SipHeader; -import io.pkts.packet.sip.header.ToHeader; -import io.pkts.packet.sip.header.ViaHeader; +import io.pkts.packet.sip.header.*; import io.pkts.packet.sip.header.impl.SipHeaderImpl; import java.io.IOException; @@ -32,9 +20,6 @@ import java.util.function.Function; - - - /** * Basic sip parser that contains most (all?) of the different grammar rules for SIP as defined by * RFC 3261. View these various methods as building blocks for building up a complete SIP parser @@ -240,6 +225,9 @@ public class SipParser { framers.put(ViaHeader.NAME, header -> ViaHeader.frame(header.getValue())); framers.put(ViaHeader.COMPACT_NAME, header -> ViaHeader.frame(header.getValue())); + + //新增WWWAuthenticateHeader注册 + framers.put(WWWAuthenticateHeader.NAME, header -> WWWAuthenticateHeader.frame(header.getValue())); } /** @@ -342,7 +330,7 @@ public static boolean isWSS(final Buffer t) { public static boolean isSCTP(final Buffer t) { try { return t.capacity() == 4 && t.getByte(0) == 'S' && t.getByte(1) == 'C' && t.getByte(2) == 'T' - && t.getByte(3) == 'P'; + && t.getByte(3) == 'P'; } catch (final IOException e) { return false; } @@ -401,7 +389,7 @@ public static boolean isWSSLower(final Buffer t) { public static boolean isSCTPLower(final Buffer t) { try { return t.capacity() == 4 && t.getByte(0) == 's' && t.getByte(1) == 'c' && t.getByte(2) == 't' - && t.getByte(3) == 'p'; + && t.getByte(3) == 'p'; } catch (final IOException e) { return false; } @@ -425,7 +413,7 @@ public static boolean isSips(final Buffer buffer) throws SipParseException, Inde if (b != 's') { throw new SipParseException(buffer.getReaderIndex() - 1, - "Expected 's' since the only schemes accepted are \"sip\" and \"sips\""); + "Expected 's' since the only schemes accepted are \"sip\" and \"sips\""); } SipParser.expect(buffer, SipParser.COLON); @@ -457,7 +445,7 @@ public static boolean isSips(final Buffer buffer) throws SipParseException, Inde * @throws IndexOutOfBoundsException */ public static List consumeGenericParams(final Buffer buffer) throws IndexOutOfBoundsException, - IOException { + IOException { final List params = new ArrayList(); while (buffer.hasReadableBytes() && buffer.peekByte() == SEMI) { buffer.readByte(); // consume the SEMI @@ -511,7 +499,7 @@ public static Buffer[] consumeGenericParam(final Buffer buffer) throws IndexOutO } } return new Buffer[]{ - key, value}; + key, value}; } /** @@ -529,6 +517,28 @@ public static boolean isNext(final Buffer buffer, final byte b) throws IOExcepti return buffer.hasReadableBytes() && buffer.peekByte() == b; } + public static boolean isNext(final Buffer buffer, final byte[] bytes) throws IOException { + boolean hasReadableBytes = buffer.hasReadableBytes(); + if (!hasReadableBytes) { + return false; + } + int readableBytes = buffer.getReadableBytes(); + int length = bytes.length; + if (readableBytes < length) { + return false; + } + boolean match = true; + for (int i = 0; i < length; i++) { + int readIndex = buffer.getReaderIndex() + i; + byte aByte = buffer.getByte(readIndex); + if (aByte != bytes[i]) { + match = false; + break; + } + } + return match; + } + /** * Check whether the next byte is a digit or not * @@ -692,7 +702,7 @@ public static void expectSLASH(final Buffer buffer) throws SipParseException { * @throws IndexOutOfBoundsException */ public static int consumeSLASH(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException, - IOException { + IOException { return consumeSeparator(buffer, SLASH); } @@ -715,7 +725,7 @@ public static int expectWS(final Buffer buffer) throws SipParseException { } } else { throw new SipParseException(buffer.getReaderIndex(), - "Expected WS but nothing more to read in the buffer"); + "Expected WS but nothing more to read in the buffer"); } } catch (final IOException e) { throw new SipParseException(buffer.getReaderIndex(), UNABLE_TO_READ_FROM_STREAM, e); @@ -752,7 +762,7 @@ public static int consumeSWS(final Buffer buffer) { * @throws IndexOutOfBoundsException */ public static int consumeSTAR(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException, - IOException { + IOException { return consumeSeparator(buffer, STAR); } @@ -843,7 +853,7 @@ public static int consumeLAQUOT(final Buffer buffer) throws IndexOutOfBoundsExce * @throws IndexOutOfBoundsException */ public static int consumeCOMMA(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException, - IOException { + IOException { return consumeSeparator(buffer, COMMA); } @@ -859,7 +869,7 @@ public static int consumeCOMMA(final Buffer buffer) throws SipParseException, In * @throws IndexOutOfBoundsException */ public static int consumeSEMI(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException, - IOException { + IOException { return consumeSeparator(buffer, SEMI); } @@ -875,7 +885,7 @@ public static int consumeSEMI(final Buffer buffer) throws SipParseException, Ind * @throws IndexOutOfBoundsException */ public static int consumeCOLON(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException, - IOException { + IOException { return consumeSeparator(buffer, COLON); } @@ -891,7 +901,7 @@ public static int consumeCOLON(final Buffer buffer) throws SipParseException, In * @throws IndexOutOfBoundsException */ public static int consumeLDQUOT(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException, - IOException { + IOException { buffer.markReaderIndex(); int consumed = consumeSWS(buffer); if (isNext(buffer, DQUOT)) { @@ -916,7 +926,7 @@ public static int consumeLDQUOT(final Buffer buffer) throws SipParseException, I * @throws IndexOutOfBoundsException */ public static int consumeRDQUOT(final Buffer buffer) throws SipParseException, IndexOutOfBoundsException, - IOException { + IOException { buffer.markReaderIndex(); int consumed = 0; if (isNext(buffer, DQUOT)) { @@ -956,7 +966,7 @@ public static int consumeRDQUOT(final Buffer buffer) throws SipParseException, I * @throws IndexOutOfBoundsException */ private static int consumeSeparator(final Buffer buffer, final byte b) throws IndexOutOfBoundsException, - IOException { + IOException { buffer.markReaderIndex(); int consumed = consumeSWS(buffer); if (isNext(buffer, b)) { @@ -984,7 +994,7 @@ private static int consumeSeparator(final Buffer buffer, final byte b) throws In * in case there is no token */ public static Buffer expectToken(final Buffer buffer) throws IndexOutOfBoundsException, IOException, - SipParseException { + SipParseException { final Buffer token = consumeToken(buffer); if (token == null) { throw new SipParseException(buffer.getReaderIndex(), "Expected TOKEN"); @@ -1094,7 +1104,7 @@ public static Buffer consumeDisplayName(final boolean quoted, final Buffer buffe } public static Buffer consumeAddressSpec(final Buffer buffer) throws IndexOutOfBoundsException, IOException, - SipParseException { + SipParseException { return consumeAddressSpec(false, buffer); } @@ -1135,7 +1145,7 @@ public static Buffer consumeAddressSpec(final Buffer buffer) throws IndexOutOfBo * @throws SipParseException in case we cannot successfully frame the addr-spec. */ public static Buffer consumeAddressSpec(final boolean isProtected, final Buffer buffer) throws IndexOutOfBoundsException, IOException, - SipParseException { + SipParseException { final int startIndex = buffer.getReaderIndex(); int count = 0; int state = 0; // zero is to look for colon, everything else is to find the end @@ -1150,7 +1160,7 @@ public static Buffer consumeAddressSpec(final boolean isProtected, final Buffer throw new SipParseException(buffer.getReaderIndex(), "No scheme found after 100 bytes, giving up"); } else if (count > MAX_LOOK_AHEAD) { throw new SipParseException(buffer.getReaderIndex(), - "Have not been able to find the entire addr-spec after " + count + " bytes, giving up"); + "Have not been able to find the entire addr-spec after " + count + " bytes, giving up"); } else if (state == 0 && b == COLON) { state = 1; } else if (state == 1 && (b == RAQUOT || b == CR || b == LF)) { @@ -1339,7 +1349,7 @@ public static Object[] consumeVia(final Buffer buffer) throws SipParseException, consumeSWS(buffer); result[0] = consumeSentProtocol(buffer); - if(consumeLWS(buffer) == 0){ + if (consumeLWS(buffer) == 0) { throw new SipParseException(buffer.getReaderIndex(), "Expected at least 1 WSP"); } @@ -1361,8 +1371,8 @@ public static Object[] consumeVia(final Buffer buffer) throws SipParseException, if (count == MAX_LOOK_AHEAD) { throw new SipParseException(buffer.getReaderIndex(), - "Unable to find the parameters part of the Via-header " - + "even after searching for " + MAX_LOOK_AHEAD + " bytes."); + "Unable to find the parameters part of the Via-header " + + "even after searching for " + MAX_LOOK_AHEAD + " bytes."); } if (indexOfSemi == 0) { @@ -1370,7 +1380,7 @@ public static Object[] consumeVia(final Buffer buffer) throws SipParseException, // if there are no parameters present at all then there // is no chance of it being present. throw new SipParseException(buffer.getReaderIndex(), - "No via-parameters found. The Via-header MUST contain at least the branch parameter."); + "No via-parameters found. The Via-header MUST contain at least the branch parameter."); } buffer.setReaderIndex(index); @@ -1392,7 +1402,7 @@ public static Object[] consumeVia(final Buffer buffer) throws SipParseException, // indication an strange number of colons. May be the strange // ipv4 address after ipv6 thing which we currently dont handle throw new SipParseException(indexOfLastColon, "Found " + countOfColons + " which seems odd." - + " Expecting 0, 1, 7 or 8 colons in the Via-host:port portion. Please check your traffic"); + + " Expecting 0, 1, 7 or 8 colons in the Via-host:port portion. Please check your traffic"); } final List params = consumeGenericParams(buffer); @@ -1475,7 +1485,7 @@ public static Buffer[] consumeSentBye(final Buffer buffer) throws SipParseExcept port = consumePort(buffer); } return new Buffer[]{ - host, port}; + host, port}; } /** @@ -1538,7 +1548,7 @@ public static Buffer consumeMType(final Buffer buffer) throws SipParseException throw new SipParseException(buffer.getReaderIndex(), "Tried to consume m-type but buffer ended abruptly", e); } catch (final IOException e) { throw new SipParseException(buffer.getReaderIndex(), - "Tried to consume m-type but problem reading from underlying stream", e); + "Tried to consume m-type but problem reading from underlying stream", e); } } @@ -1569,7 +1579,7 @@ public static Buffer consumeMSubtype(final Buffer buffer) throws SipParseExcepti throw new SipParseException(buffer.getReaderIndex(), "Tried to consume m-type but buffer ended abruptly", e); } catch (final IOException e) { throw new SipParseException(buffer.getReaderIndex(), - "Tried to consume m-type but problem reading from underlying stream", e); + "Tried to consume m-type but problem reading from underlying stream", e); } } @@ -1590,8 +1600,8 @@ public static int getTokenCount(final Buffer buffer) throws IndexOutOfBoundsExce while (buffer.hasReadableBytes() && !done) { final byte b = buffer.readByte(); final boolean ok = isAlphaNum(b) || b == DASH || b == PERIOD || b == EXCLAMATIONPOINT - || b == PERCENT || b == STAR || b == UNDERSCORE || b == PLUS || b == BACKTICK - || b == TICK || b == TILDE; + || b == PERCENT || b == STAR || b == UNDERSCORE || b == PLUS || b == BACKTICK + || b == TICK || b == TILDE; if (ok) { ++count; } else { @@ -1766,7 +1776,7 @@ public static void expect(final Buffer buffer, final byte expected) throws SipPa final String actualStr = new String(new byte[]{actual}, StandardCharsets.UTF_8); final String expectedStr = new String(new byte[]{expected}); throw new SipParseException(buffer.getReaderIndex(), "Expected '" + expected + "' (" + expectedStr - + ") got '" + actual + "' (" + actualStr + ")"); + + ") got '" + actual + "' (" + actualStr + ")"); } } @@ -1912,8 +1922,9 @@ private static boolean isHeaderAllowingMultipleValues(final Buffer headerName) { return !isAllowEventsHeaderShort(headerName); } else if (size == 12) { return !isAllowEventsHeader(headerName); + } else if (size == 16) { + return !isWWWAuthenticateHeader(headerName); } - return true; } @@ -1926,7 +1937,7 @@ private static boolean isHeaderAllowingMultipleValues(final Buffer headerName) { private static boolean isDateHeader(final Buffer name) { try { return name.getByte(0) == 'D' && name.getByte(1) == 'a' && - name.getByte(2) == 't' && name.getByte(3) == 'e'; + name.getByte(2) == 't' && name.getByte(3) == 'e'; } catch (final IOException e) { return false; } @@ -1935,8 +1946,8 @@ private static boolean isDateHeader(final Buffer name) { private static boolean isAllowHeader(final Buffer name) { try { return name.getByte(0) == 'A' && name.getByte(1) == 'l' && - name.getByte(2) == 'l' && name.getByte(3) == 'o' && - name.getByte(4) == 'w'; + name.getByte(2) == 'l' && name.getByte(3) == 'o' && + name.getByte(4) == 'w'; } catch (final IOException e) { return false; } @@ -1953,11 +1964,34 @@ private static boolean isAllowEventsHeaderShort(final Buffer name) { private static boolean isAllowEventsHeader(final Buffer name) { try { return name.getByte(0) == 'A' && name.getByte(1) == 'l' && - name.getByte(2) == 'l' && name.getByte(3) == 'o' && - name.getByte(4) == 'w' && name.getByte(5) == '-' && - name.getByte(6) == 'E' && name.getByte(7) == 'v' && - name.getByte(8) == 'e' && name.getByte(9) == 'n' && - name.getByte(10) == 't' && name.getByte(11) == 's'; + name.getByte(2) == 'l' && name.getByte(3) == 'o' && + name.getByte(4) == 'w' && name.getByte(5) == '-' && + name.getByte(6) == 'E' && name.getByte(7) == 'v' && + name.getByte(8) == 'e' && name.getByte(9) == 'n' && + name.getByte(10) == 't' && name.getByte(11) == 's'; + } catch (final IOException e) { + return false; + } + } + + private static boolean isWWWAuthenticateHeader(final Buffer name) { + try { + return (name.getByte(0) == 'W' || name.getByte(0) == 'w') && + (name.getByte(1) == 'W' || name.getByte(1) == 'w') && + (name.getByte(2) == 'W' || name.getByte(2) == 'w') && + name.getByte(3) == '-' && + (name.getByte(4) == 'A' || name.getByte(4) == 'a') && + (name.getByte(5) == 'U' || name.getByte(5) == 'u') && + (name.getByte(6) == 'T' || name.getByte(6) == 't') && + (name.getByte(7) == 'H' || name.getByte(7) == 'h') && + (name.getByte(8) == 'E' || name.getByte(8) == 'e') && + (name.getByte(9) == 'N' || name.getByte(9) == 'n') && + (name.getByte(10) == 'T' || name.getByte(10) == 't') && + (name.getByte(11) == 'I' || name.getByte(11) == 'i') && + (name.getByte(12) == 'C' || name.getByte(12) == 'c') && + (name.getByte(13) == 'A' || name.getByte(13) == 'a') && + (name.getByte(14) == 'T' || name.getByte(14) == 't') && + (name.getByte(15) == 'E' || name.getByte(15) == 'e'); } catch (final IOException e) { return false; } @@ -1966,9 +2000,9 @@ private static boolean isAllowEventsHeader(final Buffer name) { private static boolean isSubjectHeader(final Buffer name) { try { return name.getByte(0) == 'S' && name.getByte(1) == 'u' && - name.getByte(2) == 'b' && name.getByte(3) == 'j' && - name.getByte(4) == 'e' && name.getByte(5) == 'c' && - name.getByte(6) == 't'; + name.getByte(2) == 'b' && name.getByte(3) == 'j' && + name.getByte(4) == 'e' && name.getByte(5) == 'c' && + name.getByte(6) == 't'; } catch (final IOException e) { return false; } @@ -2166,7 +2200,7 @@ public static SipMessage frame(final Buffer buffer) throws IOException { if (!couldBeSipMessage(buffer)) { throw new SipParseException(0, "Cannot be a SIP message because is doesnt start with \"SIP\" " - + "(for responses) or a method (for requests)"); + + "(for responses) or a method (for requests)"); } final int startIndex = buffer.getReaderIndex(); @@ -2185,6 +2219,7 @@ public static SipMessage frame(final Buffer buffer) throws IOException { SipHeader routeHeader = null; SipHeader recordRouteHeader = null; SipHeader contactHeader = null; + SipHeader wwwAuthenticateHeader = null; final Map> headers = new HashMap<>(); int contentLength = 0; @@ -2205,7 +2240,7 @@ public static SipMessage frame(final Buffer buffer) throws IOException { } else if (cSeqHeader == null && header.isCSeqHeader()) { header = header.ensure(); cSeqHeader = header; - } else if ( maxForwardsHeader == null && header.isMaxForwardsHeader()) { + } else if (maxForwardsHeader == null && header.isMaxForwardsHeader()) { header = header.ensure(); maxForwardsHeader = header; } else if (fromHeader == null && header.isFromHeader()) { @@ -2226,8 +2261,12 @@ public static SipMessage frame(final Buffer buffer) throws IOException { } else if (recordRouteHeader == null && header.isRecordRouteHeader()) { header = header.ensure(); recordRouteHeader = header; + } else if (wwwAuthenticateHeader == null && header.isWWWAuthenticateHeader()) { + header = header.ensure(); + wwwAuthenticateHeader = header; } + headers.computeIfAbsent(headerName.toString(), k -> new ArrayList<>(4)).add(header); } } @@ -2248,32 +2287,32 @@ public static SipMessage frame(final Buffer buffer) throws IOException { if (initialLine.isRequestLine()) { return new ImmutableSipRequest(msg, - initialLine.toRequestLine(), - headers, - toHeader, - fromHeader, - cSeqHeader, - callIdHeader, - maxForwardsHeader, - viaHeader, - routeHeader, - recordRouteHeader, - contactHeader, - payload); + initialLine.toRequestLine(), + headers, + toHeader, + fromHeader, + cSeqHeader, + callIdHeader, + maxForwardsHeader, + viaHeader, + routeHeader, + recordRouteHeader, + contactHeader, + payload); } else { return new ImmutableSipResponse(msg, - initialLine.toResponseLine(), - headers, - toHeader, - fromHeader, - cSeqHeader, - callIdHeader, - maxForwardsHeader, - viaHeader, - routeHeader, - recordRouteHeader, - contactHeader, - payload); + initialLine.toResponseLine(), + headers, + toHeader, + fromHeader, + cSeqHeader, + callIdHeader, + maxForwardsHeader, + viaHeader, + routeHeader, + recordRouteHeader, + contactHeader, + payload); } } @@ -2293,20 +2332,20 @@ public static boolean couldBeSipMessage(final Buffer data) throws IOException { public static boolean couldBeSipMessage(final byte a, final byte b, final byte c) throws IOException { return a == 'S' && b == 'I' && c == 'P' || // response - a == 'I' && b == 'N' && c == 'V' || // INVITE - a == 'A' && b == 'C' && c == 'K' || // ACK - a == 'B' && b == 'Y' && c == 'E' || // BYE - a == 'O' && b == 'P' && c == 'T' || // OPTIONS - a == 'C' && b == 'A' && c == 'N' || // CANCEL - a == 'M' && b == 'E' && c == 'S' || // MESSAGE - a == 'R' && b == 'E' && c == 'G' || // REGISTER - a == 'I' && b == 'N' && c == 'F' || // INFO - a == 'P' && b == 'R' && c == 'A' || // PRACK - a == 'S' && b == 'U' && c == 'B' || // SUBSCRIBE - a == 'N' && b == 'O' && c == 'T' || // NOTIFY - a == 'U' && b == 'P' && c == 'D' || // UPDATE - a == 'R' && b == 'E' && c == 'F' || // REFER - a == 'P' && b == 'U' && c == 'B'; // PUBLISH + a == 'I' && b == 'N' && c == 'V' || // INVITE + a == 'A' && b == 'C' && c == 'K' || // ACK + a == 'B' && b == 'Y' && c == 'E' || // BYE + a == 'O' && b == 'P' && c == 'T' || // OPTIONS + a == 'C' && b == 'A' && c == 'N' || // CANCEL + a == 'M' && b == 'E' && c == 'S' || // MESSAGE + a == 'R' && b == 'E' && c == 'G' || // REGISTER + a == 'I' && b == 'N' && c == 'F' || // INFO + a == 'P' && b == 'R' && c == 'A' || // PRACK + a == 'S' && b == 'U' && c == 'B' || // SUBSCRIBE + a == 'N' && b == 'O' && c == 'T' || // NOTIFY + a == 'U' && b == 'P' && c == 'D' || // UPDATE + a == 'R' && b == 'E' && c == 'F' || // REFER + a == 'P' && b == 'U' && c == 'B'; // PUBLISH } diff --git a/pkts-sip/src/test/java/io/pkts/packet/sip/SipRequestTest.java b/pkts-sip/src/test/java/io/pkts/packet/sip/SipRequestTest.java index 3478e956..9fc5338b 100644 --- a/pkts-sip/src/test/java/io/pkts/packet/sip/SipRequestTest.java +++ b/pkts-sip/src/test/java/io/pkts/packet/sip/SipRequestTest.java @@ -1,5 +1,5 @@ /** - * + * */ package io.pkts.packet.sip; @@ -8,20 +8,17 @@ import io.pkts.buffer.Buffer; import io.pkts.buffer.Buffers; import io.pkts.packet.sip.address.SipURI; -import io.pkts.packet.sip.header.CSeqHeader; -import io.pkts.packet.sip.header.CallIdHeader; -import io.pkts.packet.sip.header.ContactHeader; -import io.pkts.packet.sip.header.FromHeader; -import io.pkts.packet.sip.header.MaxForwardsHeader; -import io.pkts.packet.sip.header.RecordRouteHeader; -import io.pkts.packet.sip.header.RouteHeader; -import io.pkts.packet.sip.header.SipHeader; -import io.pkts.packet.sip.header.ViaHeader; +import io.pkts.packet.sip.header.*; +import io.pkts.packet.sip.impl.SipParser; import org.junit.Before; import org.junit.Test; +import java.io.IOException; import java.util.List; +import java.util.Optional; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; @@ -35,6 +32,131 @@ public class SipRequestTest extends PktsTestBase { private FromHeader from; + + @Test + public void testBuildRegisterResponse() throws IOException { + StringBuilder register = new StringBuilder("REGISTER sip:10.32.26.25:5070;transport=tcp SIP/2.0\r\n" + + "Via: SIP/2.0/TCP 10.32.26.25:51696;rport;branch=z9hG4bKPj8d4db68b24754f539dbf3b563a44fe55;alias\r\n" + + "Max-Forwards: 70\r\n" + + "From: ;tag=89aefb1f3fc0413283a453eda5407f60\r\n" + + "To: jimmy\r\n" + + "Call-ID: 1e7af0e67a5044658fc7f6716d329642\r\n" + + "CSeq: 36850 REGISTER\r\n" + + "User-Agent: MicroSIP/3.20.3\r\n" + + "Supported: outbound, path\r\n" + + "Contact: ;reg-id=1;+sip.instance=\"\"\r\n" + + "Expires: 300\r\n" + + "Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS\r\n" + + "Content-Length: 0\r\n"); + SipMessage msgMessage = SipParser.frame(Buffers.wrap(register.toString())); + + SipResponse sipResponse = msgMessage.createResponse(401) + .withHeader(SipHeader.create("User-Agent", "FreeSWITCH-mod_sofia/1.6.18+git~20170612T211449Z~6e79667c0a~64bit")) + .withHeader(SipHeader.create("Allow", "INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE")) + .withHeader(SipHeader.create("Supported", "timer, path, replaces")) + .withHeader(SipHeader.create("WWW-Authenticate", "Digest realm=\"10.32.26.25\", nonce=\"bee3366b-cf59-476e-bc5e-334e0d65b386\", algorithm=MD5, qop=\"auth\"")) + .withHeader(new ContentLengthHeader.Builder(0).build()) + .build(); + + System.out.println(sipResponse); + + } + + @Test + public void testParseRegisterResponse() throws IOException { + StringBuilder register = new StringBuilder("SIP/2.0 401 Unauthorized\r\n" + + "Via: SIP/2.0/TCP 10.32.26.25:51696;rport=51696;branch=z9hG4bKPj8d4db68b24754f539dbf3b563a44fe55;alias\r\n" + + "From: ;tag=89aefb1f3fc0413283a453eda5407f60\r\n" + + "To: ;tag=Q0m1g96BS3vpa\r\n" + + "Call-ID: 1e7af0e67a5044658fc7f6716d329642\r\n" + + "CSeq: 36850 REGISTER\r\n" + + "User-Agent: FreeSWITCH-mod_sofia/1.6.18+git~20170612T211449Z~6e79667c0a~64bit\r\n" + + "Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE\r\n" + + "Supported: timer, path, replaces\r\n" + + "WWW-Authenticate: Digest realm=\"10.32.26.25\", nonce=\"bee3366b-cf59-476e-bc5e-334e0d65b386\", algorithm=MD5, qop=\"auth\"\r\n" + + "Content-Length: 0"); + + SipMessage msgMessage = SipParser.frame(Buffers.wrap(register.toString())); + + WWWAuthenticateHeader wwwAuthenticateHeader = msgMessage.getWWWAuthenticateHeader(); + + List wwwAuthenticateList = msgMessage.getAllHeaders() + .stream() + .filter(c -> c.getName().equalsIgnoreCase(Buffers.wrap("WWW-Authenticate"))) + .collect(Collectors.toList()); + System.out.println(wwwAuthenticateList); + } + + + + @Test + public void testParseRegister() throws IOException { + StringBuilder register = new StringBuilder("REGISTER sip:10.32.26.25:5070;transport=tcp SIP/2.0\r\n" + + "Via: SIP/2.0/TCP 10.32.26.25:51696;rport;branch=z9hG4bKPj8d4db68b24754f539dbf3b563a44fe55;alias\r\n" + + "Max-Forwards: 70\r\n" + + "From: ;tag=89aefb1f3fc0413283a453eda5407f60\r\n" + + "To: jimmy\r\n" + + "Call-ID: 1e7af0e67a5044658fc7f6716d329642\r\n" + + "CSeq: 36850 REGISTER\r\n" + + "User-Agent: MicroSIP/3.20.3\r\n" + + "Supported: outbound, path\r\n" + + "Contact: ;reg-id=1;+sip.instance=\"\"\r\n" + + "Expires: 300\r\n" + + "Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, INFO, SUBSCRIBE, NOTIFY, REFER, MESSAGE, OPTIONS\r\n" + + "Content-Length: 0\r\n"); + + SipMessage msgMessage = SipParser.frame(Buffers.wrap(register.toString())); + + if (msgMessage.isRegisterRequest()) { + System.out.println("This is a REGISTER request"); + } + + Buffer method = msgMessage.getMethod(); + System.out.println("方法:" + method + "\n"); + Buffer initialLine = msgMessage.getInitialLine(); + System.out.println("第一行:" + initialLine + "\n"); + + List viaHeaders = msgMessage.getViaHeaders(); + System.out.println("via:"); + for (ViaHeader viaHeader : viaHeaders) { + System.out.println("host:" + viaHeader.getHost() + ",branch:" + viaHeader.getBranch() + ",alias:" + viaHeader.getParameter("alias")); + } + + MaxForwardsHeader maxForwards = msgMessage.getMaxForwards(); + System.out.println("\nmaxForwards:" + maxForwards.getMaxForwards()); + + FromHeader fromHeader = msgMessage.getFromHeader(); + System.out.println("\nfrom-tag:" + fromHeader.getTag()); + + ToHeader toHeader = msgMessage.getToHeader(); + System.out.println("\nto:" + toHeader.getAddress().getDisplayName()); + + CallIdHeader callIDHeader = msgMessage.getCallIDHeader(); + System.out.println("\ncallId:" + callIDHeader.getCallId()); + + CSeqHeader cSeqHeader = msgMessage.getCSeqHeader(); + System.out.println("\ncSeq:" + cSeqHeader.getSeqNumber()); + + Optional userAgentHeader = msgMessage.getHeader("User-Agent"); + System.out.println("\nuserAgent value:" + userAgentHeader.get().getValue()); + + Optional supported = msgMessage.getHeader("Supported"); + System.out.println("\nsupported name:" + supported.get().getName()); + + ContactHeader contactHeader = msgMessage.getContactHeader(); + System.out.println("\ncontact reg-id:" + contactHeader.getParameter("reg-id")); + + ExpiresHeader expiresHeader = msgMessage.getExpiresHeader(); + System.out.println("\nexpires:" + expiresHeader.getExpires()); + + Optional allowHeader = msgMessage.getHeader("Allow"); + System.out.println("\nallow:" + allowHeader.get().getValue()); + + int contentLength = msgMessage.getContentLength(); + System.out.println("\ncontentLength:" + contentLength); + + } + @Override @Before public void setUp() throws Exception { @@ -124,7 +246,7 @@ private static void assertFromHeaderTag(final String from, final String expected * * @throws Exception */ - @Test(expected = SipParseException.class ) + @Test(expected = SipParseException.class) public void testBlowUpOnNoFunctionToHandleEmptyTopMostVia() throws Exception { parseMessage(RawData.sipInviteOneRecordRouteHeader) .copy() @@ -140,7 +262,7 @@ public void testBlowUpOnNoFunctionToHandleEmptyTopMostVia() throws Exception { * * @throws Exception */ - @Test(expected = SipParseException.class ) + @Test(expected = SipParseException.class) public void testBlowUpOnNoFunctionToHandleEmptyVia() throws Exception { parseMessage(RawData.sipInviteOneRecordRouteHeader) .copy() @@ -175,8 +297,8 @@ public void testCreateB2BUARequest() throws Exception { // Ensure that the from-tags now are different and that the two are actually // set in the first place. - assertThat(req.getFromHeader().getTag(), not((Buffer)null)); - assertThat(b2bua.getFromHeader().getTag(), not((Buffer)null)); + assertThat(req.getFromHeader().getTag(), not((Buffer) null)); + assertThat(b2bua.getFromHeader().getTag(), not((Buffer) null)); assertThat(req.getFromHeader().getTag(), not(b2bua.getFromHeader().getTag())); // ensure the Vias are correct. We pushed one, which should be at the top @@ -193,12 +315,12 @@ public void testCreateB2BUARequest() throws Exception { // make sure the body is left intact as well. final String expectedContent = "v=0\r\n" - + "o=user1 53655765 2353687637 IN IP4 192.168.8.110\r\n" - + "s=-\r\n" - + "c=IN IP4 192.168.8.110\r\n" - + "t=0 0\r\n" - + "m=audio 6000 RTP/AVP 0\r\n" - + "a=rtpmap:0 PCMU/8000\r\n"; + + "o=user1 53655765 2353687637 IN IP4 192.168.8.110\r\n" + + "s=-\r\n" + + "c=IN IP4 192.168.8.110\r\n" + + "t=0 0\r\n" + + "m=audio 6000 RTP/AVP 0\r\n" + + "a=rtpmap:0 PCMU/8000\r\n"; assertThat(b2bua.getContent().toString(), is(expectedContent)); @@ -347,7 +469,7 @@ public void testCreateResponse() throws Exception { /** * Test to create a new INVITE request and check all the headers that are supposed to be created * by default when not specified indeed are created with the correct values. - * + * * @throws Exception */ @Test @@ -371,7 +493,7 @@ public void testCreateInvite() throws Exception { /** * Although not mandatory from the builder's perspective, having a request without a * {@link ContactHeader} is pretty much useless so make sure that we can add that as well. - * + * * @throws Exception */ @Test @@ -395,7 +517,7 @@ public void testCreateInviteWithViaHeaders() throws Exception { assertThat(invite.getViaHeaders().size(), is(1)); assertThat( invite.getViaHeaders().get(0).toString() - .startsWith("Via: SIP/2.0/UDP 127.0.0.1:9898;branch=z9hG4bK"), is(true)); + .startsWith("Via: SIP/2.0/UDP 127.0.0.1:9898;branch=z9hG4bK"), is(true)); assertThat(invite.getViaHeader().toString().startsWith("Via: SIP/2.0/UDP 127.0.0.1:9898;branch=z9hG4bK"), is(true)); diff --git a/pkts-sip/src/test/java/io/pkts/packet/sip/header/impl/WWWAuthenticateHeaderImplTest.java b/pkts-sip/src/test/java/io/pkts/packet/sip/header/impl/WWWAuthenticateHeaderImplTest.java new file mode 100644 index 00000000..4ef1e9a9 --- /dev/null +++ b/pkts-sip/src/test/java/io/pkts/packet/sip/header/impl/WWWAuthenticateHeaderImplTest.java @@ -0,0 +1,77 @@ +package io.pkts.packet.sip.header.impl; + +import io.pkts.buffer.Buffer; +import io.pkts.buffer.Buffers; +import io.pkts.packet.sip.SipParseException; +import io.pkts.packet.sip.header.ViaHeader; +import io.pkts.packet.sip.header.WWWAuthenticateHeader; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.*; + + +public class WWWAuthenticateHeaderImplTest { + + @Test + public void testBuild1() throws Exception { + final WWWAuthenticateHeader wwwAuthenticateHeader = new WWWAuthenticateHeader.Builder() + .withAlgorithm(Buffers.wrap("MD5")) + .withNonce(Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386")) + .withQop(Buffers.wrap("auth")) + .withRealm(Buffers.wrap("10.32.26.25")) + .build(); + + assertEquals(wwwAuthenticateHeader.getAlgorithm(), Buffers.wrap("MD5")); + assertEquals(wwwAuthenticateHeader.getNonce(), Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386")); + assertEquals(wwwAuthenticateHeader.getQop(), Buffers.wrap("auth")); + assertEquals(wwwAuthenticateHeader.getRealm(), Buffers.wrap("10.32.26.25")); + + Buffer value = Buffers.wrap("Digest realm=\"10.32.26.25\", nonce=\"bee3366b-cf59-476e-bc5e-334e0d65b386\", algorithm=MD5, qop=\"auth\""); + assertTrue(wwwAuthenticateHeader.getValue().equalsIgnoreCase(value)); + } + + @Test + public void testBuild2() throws Exception { + final WWWAuthenticateHeader wwwAuthenticateHeader = new WWWAuthenticateHeader.Builder() + .withNonce(Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386")) + .withRealm(Buffers.wrap("10.32.26.25")) + .build(); + + assertEquals(wwwAuthenticateHeader.getAlgorithm(), null); + assertEquals(wwwAuthenticateHeader.getNonce(), Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386")); + assertEquals(wwwAuthenticateHeader.getQop(), null); + assertEquals(wwwAuthenticateHeader.getRealm(), Buffers.wrap("10.32.26.25")); + + Buffer value = Buffers.wrap("Digest realm=\"10.32.26.25\", nonce=\"bee3366b-cf59-476e-bc5e-334e0d65b386\""); + assertTrue(wwwAuthenticateHeader.getValue().equalsIgnoreCase(value)); + } + + @Test + public void testFrame1() throws Exception { + Buffer value = Buffers.wrap("Digest realm=\"10.32.26.25\", nonce=\"bee3366b-cf59-476e-bc5e-334e0d65b386\", algorithm=MD5, qop=\"auth\""); + final WWWAuthenticateHeader wwwAuthenticateHeader = new WWWAuthenticateHeaderImpl(value); + assertEquals(wwwAuthenticateHeader.getAlgorithm(), Buffers.wrap("MD5")); + assertEquals(wwwAuthenticateHeader.getNonce(), Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386")); + assertEquals(wwwAuthenticateHeader.getQop(), Buffers.wrap("auth")); + assertEquals(wwwAuthenticateHeader.getRealm(), Buffers.wrap("10.32.26.25")); + } + + @Test + public void testFrame2() throws Exception { + Buffer realm = Buffers.wrap("10.32.26.25"); + Buffer nonce = Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386"); + final WWWAuthenticateHeader wwwAuthenticateHeader = new WWWAuthenticateHeaderImpl(realm, nonce, null, null); + + assertEquals(wwwAuthenticateHeader.getAlgorithm(), null); + assertEquals(wwwAuthenticateHeader.getNonce(), Buffers.wrap("bee3366b-cf59-476e-bc5e-334e0d65b386")); + assertEquals(wwwAuthenticateHeader.getQop(), null); + assertEquals(wwwAuthenticateHeader.getRealm(), Buffers.wrap("10.32.26.25")); + + Buffer value = Buffers.wrap("Digest realm=\"10.32.26.25\", nonce=\"bee3366b-cf59-476e-bc5e-334e0d65b386\""); + assertTrue(wwwAuthenticateHeader.getValue().equalsIgnoreCase(value)); + } + + +}