001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk;
019
020
021import java.net.MalformedURLException;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.net.URL;
025import java.util.*;
026
027import net.jcip.annotations.Immutable;
028
029import com.nimbusds.jwt.JWT;
030import com.nimbusds.jwt.JWTClaimsSet;
031import com.nimbusds.jwt.JWTParser;
032import com.nimbusds.oauth2.sdk.http.HTTPRequest;
033import com.nimbusds.oauth2.sdk.id.ClientID;
034import com.nimbusds.oauth2.sdk.id.State;
035import com.nimbusds.oauth2.sdk.pkce.CodeChallenge;
036import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
037import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
038import com.nimbusds.oauth2.sdk.util.*;
039import com.nimbusds.openid.connect.sdk.Prompt;
040
041
042/**
043 * Authorisation request. Used to authenticate an end-user and request the
044 * end-user's consent to grant the client access to a protected resource.
045 * Supports custom request parameters.
046 *
047 * <p>Extending classes may define additional request parameters as well as 
048 * enforce tighter requirements on the base parameters.
049 *
050 * <p>Example HTTP request:
051 *
052 * <pre>
053 * https://server.example.com/authorize?
054 * response_type=code
055 * &amp;client_id=s6BhdRkqt3
056 * &amp;state=xyz
057 * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
058 * </pre>
059 *
060 * <p>Related specifications:
061 *
062 * <ul>
063 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.1 and 4.2.1.
064 *     <li>OAuth 2.0 Multiple Response Type Encoding Practices 1.0.
065 *     <li>OAuth 2.0 Form Post Response Mode 1.0.
066 *     <li>Proof Key for Code Exchange by OAuth Public Clients (RFC 7636).
067 *     <li>Resource Indicators for OAuth 2.0
068 *         (draft-ietf-oauth-resource-indicators-00)
069 *     <li>OAuth 2.0 Incremental Authorization
070 *         (draft-ietf-oauth-incremental-authz-00)
071 *     <li>The OAuth 2.0 Authorization Framework: JWT Secured Authorization
072 *         Request (JAR) draft-ietf-oauth-jwsreq-17
073 *     <li>Financial-grade API: JWT Secured Authorization Response Mode for
074 *         OAuth 2.0 (JARM)
075 * </ul>
076 */
077@Immutable
078public class AuthorizationRequest extends AbstractRequest {
079
080
081        /**
082         * The registered parameter names.
083         */
084        private static final Set<String> REGISTERED_PARAMETER_NAMES;
085
086
087        static {
088                Set<String> p = new HashSet<>();
089
090                p.add("response_type");
091                p.add("client_id");
092                p.add("redirect_uri");
093                p.add("scope");
094                p.add("state");
095                p.add("response_mode");
096                p.add("code_challenge");
097                p.add("code_challenge_method");
098                p.add("resource");
099                p.add("include_granted_scopes");
100                p.add("request_uri");
101                p.add("request");
102                p.add("prompt");
103
104                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
105        }
106        
107        
108        /**
109         * The response type (required unless in JAR).
110         */
111        private final ResponseType rt;
112
113
114        /**
115         * The client identifier (required unless in JAR).
116         */
117        private final ClientID clientID;
118
119
120        /**
121         * The redirection URI where the response will be sent (optional). 
122         */
123        private final URI redirectURI;
124        
125        
126        /**
127         * The scope (optional).
128         */
129        private final Scope scope;
130        
131        
132        /**
133         * The opaque value to maintain state between the request and the 
134         * callback (recommended).
135         */
136        private final State state;
137
138
139        /**
140         * The response mode (optional).
141         */
142        private final ResponseMode rm;
143
144
145        /**
146         * The authorisation code challenge for PKCE (optional).
147         */
148        private final CodeChallenge codeChallenge;
149
150
151        /**
152         * The authorisation code challenge method for PKCE (optional).
153         */
154        private final CodeChallengeMethod codeChallengeMethod;
155        
156        
157        /**
158         * The resource URI(s) (optional).
159         */
160        private final List<URI> resources;
161        
162        
163        /**
164         * Indicates incremental authorisation (optional).
165         */
166        private final boolean includeGrantedScopes;
167        
168        
169        /**
170         * Request object (optional).
171         */
172        private final JWT requestObject;
173        
174        
175        /**
176         * Request object URI (optional).
177         */
178        private final URI requestURI;
179        
180        
181        /**
182         * The requested prompt (optional).
183         */
184        protected final Prompt prompt;
185
186
187        /**
188         * Custom parameters.
189         */
190        private final Map<String,List<String>> customParams;
191
192
193        /**
194         * Builder for constructing authorisation requests.
195         */
196        public static class Builder {
197
198
199                /**
200                 * The endpoint URI (optional).
201                 */
202                private URI uri;
203
204
205                /**
206                 * The response type (required unless in JAR).
207                 */
208                private ResponseType rt;
209
210
211                /**
212                 * The client identifier (required unless in JAR).
213                 */
214                private ClientID clientID;
215
216
217                /**
218                 * The redirection URI where the response will be sent
219                 * (optional).
220                 */
221                private URI redirectURI;
222
223
224                /**
225                 * The scope (optional).
226                 */
227                private Scope scope;
228
229
230                /**
231                 * The opaque value to maintain state between the request and
232                 * the callback (recommended).
233                 */
234                private State state;
235
236
237                /**
238                 * The response mode (optional).
239                 */
240                private ResponseMode rm;
241
242
243                /**
244                 * The authorisation code challenge for PKCE (optional).
245                 */
246                private CodeChallenge codeChallenge;
247
248
249                /**
250                 * The authorisation code challenge method for PKCE (optional).
251                 */
252                private CodeChallengeMethod codeChallengeMethod;
253                
254                
255                /**
256                 * Indicates incremental authorisation (optional).
257                 */
258                private boolean includeGrantedScopes;
259                
260                
261                /**
262                 * The resource URI(s) (optional).
263                 */
264                private List<URI> resources;
265                
266                
267                /**
268                 * Request object (optional).
269                 */
270                private JWT requestObject;
271                
272                
273                /**
274                 * Request object URI (optional).
275                 */
276                private URI requestURI;
277                
278                
279                /**
280                 * The requested prompt (optional).
281                 */
282                private Prompt prompt;
283
284
285                /**
286                 * Custom parameters.
287                 */
288                private final Map<String,List<String>> customParams = new HashMap<>();
289
290
291                /**
292                 * Creates a new authorisation request builder.
293                 *
294                 * @param rt       The response type. Corresponds to the
295                 *                 {@code response_type} parameter. Must not be
296                 *                 {@code null}.
297                 * @param clientID The client identifier. Corresponds to the
298                 *                 {@code client_id} parameter. Must not be
299                 *                 {@code null}.
300                 */
301                public Builder(final ResponseType rt, final ClientID clientID) {
302
303                        if (rt == null)
304                                throw new IllegalArgumentException("The response type must not be null");
305
306                        this.rt = rt;
307
308
309                        if (clientID == null)
310                                throw new IllegalArgumentException("The client ID must not be null");
311
312                        this.clientID = clientID;
313                }
314                
315                
316                /**
317                 * Creates a new JWT secured authorisation request builder.
318                 *
319                 * @param requestObject The request object. Must not be
320                 *                      {@code null}.
321                 */
322                public Builder(final JWT requestObject) {
323                        
324                        if (requestObject == null)
325                                throw new IllegalArgumentException("The request object must not be null");
326                        
327                        this.requestObject = requestObject;
328                }
329                
330                
331                /**
332                 * Creates a new JWT secured authorisation request builder.
333                 *
334                 * @param requestURI The request object URI. Must not be
335                 *                   {@code null}.
336                 */
337                public Builder(final URI requestURI) {
338                        
339                        if (requestURI == null)
340                                throw new IllegalArgumentException("The request URI must not be null");
341                        
342                        this.requestURI = requestURI;
343                }
344                
345                
346                /**
347                 * Creates a new authorisation request builder from the
348                 * specified request.
349                 *
350                 * @param request The authorisation request. Must not be
351                 *                {@code null}.
352                 */
353                public Builder(final AuthorizationRequest request) {
354                        
355                        uri = request.getEndpointURI();
356                        scope = request.scope;
357                        rt = request.getResponseType();
358                        clientID = request.getClientID();
359                        redirectURI = request.getRedirectionURI();
360                        state = request.getState();
361                        rm = request.getResponseMode();
362                        codeChallenge = request.getCodeChallenge();
363                        codeChallengeMethod = request.getCodeChallengeMethod();
364                        resources = request.getResources();
365                        includeGrantedScopes = request.includeGrantedScopes();
366                        requestObject = request.requestObject;
367                        requestURI = request.requestURI;
368                        prompt = request.prompt;
369                        customParams.putAll(request.getCustomParameters());
370                }
371                
372                
373                /**
374                 * Sets the response type. Corresponds to the
375                 * {@code response_type} parameter.
376                 *
377                 * @param rt The response type. Must not be {@code null}.
378                 *
379                 * @return This builder.
380                 */
381                public Builder responseType(final ResponseType rt) {
382                        
383                        if (rt == null)
384                                throw new IllegalArgumentException("The response type must not be null");
385                        
386                        this.rt = rt;
387                        return this;
388                }
389                
390                
391                /**
392                 * Sets the client identifier. Corresponds to the
393                 * {@code client_id} parameter.
394                 *
395                 * @param clientID The client identifier. Must not be
396                 *                 {@code null}.
397                 *
398                 * @return This builder.
399                 */
400                public Builder clientID(final ClientID clientID) {
401                        
402                        if (clientID == null)
403                                throw new IllegalArgumentException("The client ID must not be null");
404                        
405                        this.clientID = clientID;
406                        return this;
407                }
408
409
410                /**
411                 * Sets the redirection URI. Corresponds to the optional
412                 * {@code redirection_uri} parameter.
413                 *
414                 * @param redirectURI The redirection URI, {@code null} if not
415                 *                    specified.
416                 *
417                 * @return This builder.
418                 */
419                public Builder redirectionURI(final URI redirectURI) {
420
421                        this.redirectURI = redirectURI;
422                        return this;
423                }
424
425
426                /**
427                 * Sets the scope. Corresponds to the optional {@code scope}
428                 * parameter.
429                 *
430                 * @param scope The scope, {@code null} if not specified.
431                 *
432                 * @return This builder.
433                 */
434                public Builder scope(final Scope scope) {
435
436                        this.scope = scope;
437                        return this;
438                }
439
440
441                /**
442                 * Sets the state. Corresponds to the recommended {@code state}
443                 * parameter.
444                 *
445                 * @param state The state, {@code null} if not specified.
446                 *
447                 * @return This builder.
448                 */
449                public Builder state(final State state) {
450
451                        this.state = state;
452                        return this;
453                }
454
455
456                /**
457                 * Sets the response mode. Corresponds to the optional
458                 * {@code response_mode} parameter. Use of this parameter is
459                 * not recommended unless a non-default response mode is
460                 * requested (e.g. form_post).
461                 *
462                 * @param rm The response mode, {@code null} if not specified.
463                 *
464                 * @return This builder.
465                 */
466                public Builder responseMode(final ResponseMode rm) {
467
468                        this.rm = rm;
469                        return this;
470                }
471
472
473                /**
474                 * Sets the code challenge for Proof Key for Code Exchange
475                 * (PKCE) by public OAuth clients.
476                 *
477                 * @param codeChallenge       The code challenge, {@code null}
478                 *                            if not specified.
479                 * @param codeChallengeMethod The code challenge method,
480                 *                            {@code null} if not specified.
481                 *
482                 * @return This builder.
483                 */
484                @Deprecated
485                public Builder codeChallenge(final CodeChallenge codeChallenge, final CodeChallengeMethod codeChallengeMethod) {
486
487                        this.codeChallenge = codeChallenge;
488                        this.codeChallengeMethod = codeChallengeMethod;
489                        return this;
490                }
491
492
493                /**
494                 * Sets the code challenge for Proof Key for Code Exchange
495                 * (PKCE) by public OAuth clients.
496                 *
497                 * @param codeVerifier        The code verifier to use to
498                 *                            compute the code challenge,
499                 *                            {@code null} if PKCE is not
500                 *                            specified.
501                 * @param codeChallengeMethod The code challenge method,
502                 *                            {@code null} if not specified.
503                 *                            Defaults to
504                 *                            {@link CodeChallengeMethod#PLAIN}
505                 *                            if a code verifier is specified.
506                 *
507                 * @return This builder.
508                 */
509                public Builder codeChallenge(final CodeVerifier codeVerifier, final CodeChallengeMethod codeChallengeMethod) {
510
511                        if (codeVerifier != null) {
512                                CodeChallengeMethod method = codeChallengeMethod != null ? codeChallengeMethod : CodeChallengeMethod.getDefault();
513                                this.codeChallenge = CodeChallenge.compute(method, codeVerifier);
514                                this.codeChallengeMethod = method;
515                        } else {
516                                this.codeChallenge = null;
517                                this.codeChallengeMethod = null;
518                        }
519                        return this;
520                }
521                
522                
523                /**
524                 * Sets the resource server URI(s).
525                 *
526                 * @param resources The resource URI(s), {@code null} if not
527                 *                  specified.
528                 *
529                 * @return This builder.
530                 */
531                public Builder resources(final URI ... resources) {
532                        if (resources != null) {
533                                this.resources = Arrays.asList(resources);
534                        } else {
535                                this.resources = null;
536                        }
537                        return this;
538                }
539                
540                
541                /**
542                 * Requests incremental authorisation.
543                 *
544                 * @param includeGrantedScopes {@code true} to request
545                 *                             incremental authorisation.
546                 *
547                 * @return This builder.
548                 */
549                public Builder includeGrantedScopes(final boolean includeGrantedScopes) {
550                        
551                        this.includeGrantedScopes = includeGrantedScopes;
552                        return this;
553                }
554                
555                
556                /**
557                 * Sets the request object. Corresponds to the optional
558                 * {@code request} parameter. Must not be specified together
559                 * with a request object URI.
560                 *
561                 * @param requestObject The request object, {@code null} if not
562                 *                      specified.
563                 *
564                 * @return This builder.
565                 */
566                public Builder requestObject(final JWT requestObject) {
567                        
568                        this.requestObject = requestObject;
569                        return this;
570                }
571                
572                
573                /**
574                 * Sets the request object URI. Corresponds to the optional
575                 * {@code request_uri} parameter. Must not be specified
576                 * together with a request object.
577                 *
578                 * @param requestURI The request object URI, {@code null} if
579                 *                   not specified.
580                 *
581                 * @return This builder.
582                 */
583                public Builder requestURI(final URI requestURI) {
584                        
585                        this.requestURI = requestURI;
586                        return this;
587                }
588                
589                
590                /**
591                 * Sets the requested prompt. Corresponds to the optional
592                 * {@code prompt} parameter.
593                 *
594                 * @param prompt The requested prompt, {@code null} if not
595                 *               specified.
596                 *
597                 * @return This builder.
598                 */
599                public Builder prompt(final Prompt prompt) {
600                        
601                        this.prompt = prompt;
602                        return this;
603                }
604                
605                
606                /**
607                 * Sets a custom parameter.
608                 *
609                 * @param name   The parameter name. Must not be {@code null}.
610                 * @param values The parameter values, {@code null} if not
611                 *               specified.
612                 *
613                 * @return This builder.
614                 */
615                public Builder customParameter(final String name, final String ... values) {
616
617                        if (values == null || values.length == 0) {
618                                customParams.remove(name);
619                        } else {
620                                customParams.put(name, Arrays.asList(values));
621                        }
622                        
623                        return this;
624                }
625
626
627                /**
628                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
629                 * request is intended.
630                 *
631                 * @param uri The endpoint URI, {@code null} if not specified.
632                 *
633                 * @return This builder.
634                 */
635                public Builder endpointURI(final URI uri) {
636
637                        this.uri = uri;
638                        return this;
639                }
640
641
642                /**
643                 * Builds a new authorisation request.
644                 *
645                 * @return The authorisation request.
646                 */
647                public AuthorizationRequest build() {
648
649                        try {
650                                return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state,
651                                        codeChallenge, codeChallengeMethod, resources, includeGrantedScopes,
652                                        requestObject, requestURI,
653                                        prompt,
654                                        customParams);
655                        } catch (IllegalArgumentException e) {
656                                throw new IllegalStateException(e.getMessage(), e);
657                        }
658                }
659        }
660
661
662        /**
663         * Creates a new minimal authorisation request.
664         *
665         * @param uri      The URI of the authorisation endpoint. May be
666         *                 {@code null} if the {@link #toHTTPRequest} method
667         *                 will not be used.
668         * @param rt       The response type. Corresponds to the
669         *                 {@code response_type} parameter. Must not be
670         *                 {@code null}.
671         * @param clientID The client identifier. Corresponds to the
672         *                 {@code client_id} parameter. Must not be
673         *                 {@code null}.
674         */
675        public AuthorizationRequest(final URI uri,
676                                    final ResponseType rt,
677                                    final ClientID clientID) {
678
679                this(uri, rt, null, clientID, null, null, null, null, null, null, false, null, null, null, null);
680        }
681
682
683        /**
684         * Creates a new authorisation request.
685         *
686         * @param uri                 The URI of the authorisation endpoint.
687         *                            May be {@code null} if the
688         *                            {@link #toHTTPRequest} method will not be
689         *                            used.
690         * @param rt                  The response type. Corresponds to the
691         *                            {@code response_type} parameter. Must not
692         *                            be {@code null}.
693         * @param rm                  The response mode. Corresponds to the
694         *                            optional {@code response_mode} parameter.
695         *                            Use of this parameter is not recommended
696         *                            unless a non-default response mode is
697         *                            requested (e.g. form_post).
698         * @param clientID            The client identifier. Corresponds to the
699         *                            {@code client_id} parameter. Must not be
700         *                            {@code null}.
701         * @param redirectURI         The redirection URI. Corresponds to the
702         *                            optional {@code redirect_uri} parameter.
703         *                            {@code null} if not specified.
704         * @param scope               The request scope. Corresponds to the
705         *                            optional {@code scope} parameter.
706         *                            {@code null} if not specified.
707         * @param state               The state. Corresponds to the recommended
708         *                            {@code state} parameter. {@code null} if
709         *                            not specified.
710         */
711        public AuthorizationRequest(final URI uri,
712                                    final ResponseType rt,
713                                    final ResponseMode rm,
714                                    final ClientID clientID,
715                                    final URI redirectURI,
716                                    final Scope scope,
717                                    final State state) {
718
719                this(uri, rt, rm, clientID, redirectURI, scope, state, null, null, null, false, null, null, null, null);
720        }
721
722
723        /**
724         * Creates a new authorisation request with extension and custom
725         * parameters.
726         *
727         * @param uri                  The URI of the authorisation endpoint.
728         *                             May be {@code null} if the
729         *                             {@link #toHTTPRequest} method will not
730         *                             be used.
731         * @param rt                   The response type. Corresponds to the
732         *                             {@code response_type} parameter. Must
733         *                             not be {@code null}, unless a request a
734         *                             request object or URI is specified.
735         * @param rm                   The response mode. Corresponds to the
736         *                             optional {@code response_mode}
737         *                             parameter. Use of this parameter is not
738         *                             recommended unless a non-default
739         *                             response mode is requested (e.g.
740         *                             form_post).
741         * @param clientID             The client identifier. Corresponds to
742         *                             the {@code client_id} parameter. Must
743         *                             not be {@code null}, unless a request
744         *                             object or URI is specified.
745         * @param redirectURI          The redirection URI. Corresponds to the
746         *                             optional {@code redirect_uri} parameter.
747         *                             {@code null} if not specified.
748         * @param scope                The request scope. Corresponds to the
749         *                             optional {@code scope} parameter.
750         *                             {@code null} if not specified.
751         * @param state                The state. Corresponds to the
752         *                             recommended {@code state} parameter.
753         *                             {@code null} if not specified.
754         * @param codeChallenge        The code challenge for PKCE,
755         *                             {@code null} if not specified.
756         * @param codeChallengeMethod  The code challenge method for PKCE,
757         *                             {@code null} if not specified.
758         * @param resources            The resource URI(s), {@code null} if not
759         *                             specified.
760         * @param includeGrantedScopes {@code true} to request incremental
761         *                             authorisation.
762         * @param requestObject        The request object. Corresponds to the
763         *                             optional {@code request} parameter. Must
764         *                             not be specified together with a request
765         *                             object URI. {@code null} if not
766         *                             specified.
767         * @param requestURI           The request object URI. Corresponds to
768         *                             the optional {@code request_uri}
769         *                             parameter. Must not be specified
770         *                             together with a request object.
771         *                             {@code null} if not specified.
772         * @param prompt               The requested prompt. Corresponds to the
773         *                             optional {@code prompt} parameter.
774         * @param customParams         Custom parameters, empty map or
775         *                             {@code null} if none.
776         */
777        public AuthorizationRequest(final URI uri,
778                                    final ResponseType rt,
779                                    final ResponseMode rm,
780                                    final ClientID clientID,
781                                    final URI redirectURI,
782                                    final Scope scope,
783                                    final State state,
784                                    final CodeChallenge codeChallenge,
785                                    final CodeChallengeMethod codeChallengeMethod,
786                                    final List<URI> resources,
787                                    final boolean includeGrantedScopes,
788                                    final JWT requestObject,
789                                    final URI requestURI,
790                                    final Prompt prompt,
791                                    final Map<String, List<String>> customParams) {
792
793                super(uri);
794
795                if (rt == null && requestObject == null && requestURI == null)
796                        throw new IllegalArgumentException("The response type must not be null");
797
798                this.rt = rt;
799
800                this.rm = rm;
801
802
803                if (clientID == null && requestObject == null && requestURI == null)
804                        throw new IllegalArgumentException("The client ID must not be null");
805
806                this.clientID = clientID;
807
808
809                this.redirectURI = redirectURI;
810                this.scope = scope;
811                this.state = state;
812
813                this.codeChallenge = codeChallenge;
814                this.codeChallengeMethod = codeChallengeMethod;
815                
816                if (resources != null) {
817                        for (URI resourceURI: resources) {
818                                if (! ResourceUtils.isValidResourceURI(resourceURI))
819                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
820                        }
821                }
822                
823                this.resources = resources;
824                
825                this.includeGrantedScopes = includeGrantedScopes;
826                
827                if (requestObject != null && requestURI != null)
828                        throw new IllegalArgumentException("Either a request object or a request URI must be specified, but not both");
829                
830                this.requestObject = requestObject;
831                this.requestURI = requestURI;
832                
833                this.prompt = prompt; // technically OpenID
834
835                if (MapUtils.isNotEmpty(customParams)) {
836                        this.customParams = Collections.unmodifiableMap(customParams);
837                } else {
838                        this.customParams = Collections.emptyMap();
839                }
840        }
841
842
843        /**
844         * Returns the registered (standard) OAuth 2.0 authorisation request
845         * parameter names.
846         *
847         * @return The registered OAuth 2.0 authorisation request parameter
848         *         names, as a unmodifiable set.
849         */
850        public static Set<String> getRegisteredParameterNames() {
851
852                return REGISTERED_PARAMETER_NAMES;
853        }
854
855
856        /**
857         * Gets the response type. Corresponds to the {@code response_type}
858         * parameter.
859         *
860         * @return The response type, may be {@code null} for a
861         *         {@link #specifiesRequestObject() JWT secured authorisation
862         *         request} with a {@link #getRequestObject() request} or
863         *         {@link #getRequestURI() request_uri} parameter.
864         */
865        public ResponseType getResponseType() {
866        
867                return rt;
868        }
869
870
871        /**
872         * Gets the optional response mode. Corresponds to the optional
873         * {@code response_mode} parameter.
874         *
875         * @return The response mode, {@code null} if not specified.
876         */
877        public ResponseMode getResponseMode() {
878
879                return rm;
880        }
881
882
883        /**
884         * Returns the implied response mode, determined by the optional
885         * {@code response_mode} parameter, and if that isn't specified, by
886         * the {@code response_type}.
887         *
888         * @return The implied response mode.
889         */
890        public ResponseMode impliedResponseMode() {
891                
892                return ResponseMode.resolve(rm, rt);
893        }
894
895
896        /**
897         * Gets the client identifier. Corresponds to the {@code client_id} 
898         * parameter.
899         *
900         * @return The client identifier, may be {@code null} for a
901         *         {@link #specifiesRequestObject() JWT secured authorisation
902         *         request} with a {@link #getRequestObject() request} or
903         *         {@link #getRequestURI() request_uri} parameter.
904         */
905        public ClientID getClientID() {
906        
907                return clientID;
908        }
909
910
911        /**
912         * Gets the redirection URI. Corresponds to the optional 
913         * {@code redirection_uri} parameter.
914         *
915         * @return The redirection URI, {@code null} if not specified.
916         */
917        public URI getRedirectionURI() {
918        
919                return redirectURI;
920        }
921        
922        
923        /**
924         * Gets the scope. Corresponds to the optional {@code scope} parameter.
925         *
926         * @return The scope, {@code null} if not specified.
927         */
928        public Scope getScope() {
929        
930                return scope;
931        }
932        
933        
934        /**
935         * Gets the state. Corresponds to the recommended {@code state} 
936         * parameter.
937         *
938         * @return The state, {@code null} if not specified.
939         */
940        public State getState() {
941        
942                return state;
943        }
944
945
946        /**
947         * Returns the code challenge for PKCE.
948         *
949         * @return The code challenge, {@code null} if not specified.
950         */
951        public CodeChallenge getCodeChallenge() {
952
953                return codeChallenge;
954        }
955
956
957        /**
958         * Returns the code challenge method for PKCE.
959         *
960         * @return The code challenge method, {@code null} if not specified.
961         */
962        public CodeChallengeMethod getCodeChallengeMethod() {
963
964                return codeChallengeMethod;
965        }
966        
967        
968        /**
969         * Returns the resource server URI.
970         *
971         * @return The resource URI(s), {@code null} if not specified.
972         */
973        public List<URI> getResources() {
974                
975                return resources;
976        }
977        
978        
979        /**
980         * Returns {@code true} if incremental authorisation is requested.
981         *
982         * @return {@code true} if incremental authorisation is requested,
983         *         else {@code false}.
984         */
985        public boolean includeGrantedScopes() {
986                
987                return includeGrantedScopes;
988        }
989        
990        
991        /**
992         * Gets the request object. Corresponds to the optional {@code request}
993         * parameter.
994         *
995         * @return The request object, {@code null} if not specified.
996         */
997        public JWT getRequestObject() {
998                
999                return requestObject;
1000        }
1001        
1002        
1003        /**
1004         * Gets the request object URI. Corresponds to the optional
1005         * {@code request_uri} parameter.
1006         *
1007         * @return The request object URI, {@code null} if not specified.
1008         */
1009        public URI getRequestURI() {
1010                
1011                return requestURI;
1012        }
1013        
1014        
1015        /**
1016         * Returns {@code true} if this is a JWT secured authentication
1017         * request.
1018         *
1019         * @return {@code true} if a request object via a {@code request} or
1020         *         {@code request_uri} parameter is specified, else
1021         *         {@code false}.
1022         */
1023        public boolean specifiesRequestObject() {
1024                
1025                return requestObject != null || requestURI != null;
1026        }
1027        
1028        
1029        /**
1030         * Gets the requested prompt. Corresponds to the optional
1031         * {@code prompt} parameter.
1032         *
1033         * @return The requested prompt, {@code null} if not specified.
1034         */
1035        public Prompt getPrompt() {
1036                
1037                return prompt;
1038        }
1039        
1040        
1041        /**
1042         * Returns the additional custom parameters.
1043         *
1044         * @return The additional custom parameters as a unmodifiable map,
1045         *         empty map if none.
1046         */
1047        public Map<String,List<String>> getCustomParameters () {
1048
1049                return customParams;
1050        }
1051        
1052        
1053        /**
1054         * Returns the specified custom parameter.
1055         *
1056         * @param name The parameter name. Must not be {@code null}.
1057         *
1058         * @return The parameter value(s), {@code null} if not specified.
1059         */
1060        public List<String> getCustomParameter(final String name) {
1061
1062                return customParams.get(name);
1063        }
1064        
1065        
1066        /**
1067         * Returns the URI query parameters for this authorisation request.
1068         * Query parameters which are part of the authorisation endpoint are
1069         * not included.
1070         *
1071         * <p>Example parameters:
1072         *
1073         * <pre>
1074         * response_type = code
1075         * client_id     = s6BhdRkqt3
1076         * state         = xyz
1077         * redirect_uri  = https://client.example.com/cb
1078         * </pre>
1079         * 
1080         * @return The parameters.
1081         */
1082        public Map<String,List<String>> toParameters() {
1083                
1084                // Put custom params first, so they may be overwritten by std params
1085                Map<String, List<String>> params = new LinkedHashMap<>(customParams);
1086                
1087                if (rt != null)
1088                        params.put("response_type", Collections.singletonList(rt.toString()));
1089                
1090                if (clientID != null)
1091                        params.put("client_id", Collections.singletonList(clientID.getValue()));
1092
1093                if (rm != null)
1094                        params.put("response_mode", Collections.singletonList(rm.getValue()));
1095
1096                if (redirectURI != null)
1097                        params.put("redirect_uri", Collections.singletonList(redirectURI.toString()));
1098
1099                if (scope != null)
1100                        params.put("scope", Collections.singletonList(scope.toString()));
1101                
1102                if (state != null)
1103                        params.put("state", Collections.singletonList(state.getValue()));
1104
1105                if (codeChallenge != null) {
1106                        params.put("code_challenge", Collections.singletonList(codeChallenge.getValue()));
1107
1108                        if (codeChallengeMethod != null) {
1109                                params.put("code_challenge_method", Collections.singletonList(codeChallengeMethod.getValue()));
1110                        }
1111                }
1112                
1113                if (includeGrantedScopes)
1114                        params.put("include_granted_scopes", Collections.singletonList("true"));
1115                
1116                if (resources != null) {
1117                        List<String> resourceValues = new LinkedList<>();
1118                        for (URI resourceURI: resources) {
1119                                if (resourceURI != null) {
1120                                        resourceValues.add(resourceURI.toString());
1121                                }
1122                        }
1123                        params.put("resource", resourceValues);
1124                }
1125                
1126                if (requestObject != null) {
1127                        try {
1128                                params.put("request", Collections.singletonList(requestObject.serialize()));
1129                                
1130                        } catch (IllegalStateException e) {
1131                                throw new SerializeException("Couldn't serialize request object to JWT: " + e.getMessage(), e);
1132                        }
1133                }
1134                
1135                if (requestURI != null)
1136                        params.put("request_uri", Collections.singletonList(requestURI.toString()));
1137                
1138                if (prompt != null)
1139                        params.put("prompt", Collections.singletonList(prompt.toString()));
1140
1141                return params;
1142        }
1143        
1144        
1145        /**
1146         * Returns the parameters for this authorisation request as a JSON Web
1147         * Token (JWT) claims set. Intended for creating a request object.
1148         *
1149         * @return The parameters as JWT claim set.
1150         */
1151        public JWTClaimsSet toJWTClaimsSet() {
1152                
1153                if (specifiesRequestObject()) {
1154                        throw new IllegalStateException("Cannot create nested JWT secured authorization request");
1155                }
1156                
1157                return JWTClaimsSetUtils.toJWTClaimsSet(toParameters());
1158        }
1159        
1160        
1161        /**
1162         * Returns the URI query string for this authorisation request.
1163         *
1164         * <p>Note that the '?' character preceding the query string in an URI
1165         * is not included in the returned string.
1166         *
1167         * <p>Example URI query string:
1168         *
1169         * <pre>
1170         * response_type=code
1171         * &amp;client_id=s6BhdRkqt3
1172         * &amp;state=xyz
1173         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1174         * </pre>
1175         * 
1176         * @return The URI query string.
1177         */
1178        public String toQueryString() {
1179                
1180                Map<String, List<String>> params = new HashMap<>();
1181                if (getEndpointURI() != null) {
1182                        params.putAll(URLUtils.parseParameters(getEndpointURI().getQuery()));
1183                }
1184                params.putAll(toParameters());
1185                
1186                return URLUtils.serializeParameters(params);
1187        }
1188
1189
1190        /**
1191         * Returns the complete URI representation for this authorisation
1192         * request, consisting of the {@link #getEndpointURI authorization
1193         * endpoint URI} with the {@link #toQueryString query string} appended.
1194         *
1195         * <p>Example URI:
1196         *
1197         * <pre>
1198         * https://server.example.com/authorize?
1199         * response_type=code
1200         * &amp;client_id=s6BhdRkqt3
1201         * &amp;state=xyz
1202         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1203         * </pre>
1204         *
1205         * @return The URI representation.
1206         */
1207        public URI toURI() {
1208
1209                if (getEndpointURI() == null)
1210                        throw new SerializeException("The authorization endpoint URI is not specified");
1211                
1212                StringBuilder sb = new StringBuilder(URIUtils.stripQueryString(getEndpointURI()).toString());
1213                sb.append('?');
1214                sb.append(toQueryString());
1215                try {
1216                        return new URI(sb.toString());
1217                } catch (URISyntaxException e) {
1218                        throw new SerializeException("Couldn't append query string: " + e.getMessage(), e);
1219                }
1220        }
1221        
1222        
1223        /**
1224         * Returns the matching HTTP request.
1225         *
1226         * @param method The HTTP request method which can be GET or POST. Must
1227         *               not be {@code null}.
1228         *
1229         * @return The HTTP request.
1230         */
1231        public HTTPRequest toHTTPRequest(final HTTPRequest.Method method) {
1232                
1233                if (getEndpointURI() == null)
1234                        throw new SerializeException("The endpoint URI is not specified");
1235                
1236                HTTPRequest httpRequest;
1237
1238                URL endpointURL;
1239
1240                try {
1241                        endpointURL = getEndpointURI().toURL();
1242
1243                } catch (MalformedURLException e) {
1244
1245                        throw new SerializeException(e.getMessage(), e);
1246                }
1247                
1248                if (method.equals(HTTPRequest.Method.GET)) {
1249
1250                        httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL);
1251
1252                } else if (method.equals(HTTPRequest.Method.POST)) {
1253
1254                        httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL);
1255
1256                } else {
1257
1258                        throw new IllegalArgumentException("The HTTP request method must be GET or POST");
1259                }
1260                
1261                httpRequest.setQuery(toQueryString());
1262                
1263                return httpRequest;
1264        }
1265        
1266        
1267        @Override
1268        public HTTPRequest toHTTPRequest() {
1269        
1270                return toHTTPRequest(HTTPRequest.Method.GET);
1271        }
1272
1273
1274        /**
1275         * Parses an authorisation request from the specified URI query
1276         * parameters.
1277         *
1278         * <p>Example parameters:
1279         *
1280         * <pre>
1281         * response_type = code
1282         * client_id     = s6BhdRkqt3
1283         * state         = xyz
1284         * redirect_uri  = https://client.example.com/cb
1285         * </pre>
1286         *
1287         * @param params The parameters. Must not be {@code null}.
1288         *
1289         * @return The authorisation request.
1290         *
1291         * @throws ParseException If the parameters couldn't be parsed to an
1292         *                        authorisation request.
1293         */
1294        public static AuthorizationRequest parse(final Map<String,List<String>> params)
1295                throws ParseException {
1296
1297                return parse(null, params);
1298        }
1299
1300
1301        /**
1302         * Parses an authorisation request from the specified URI and query
1303         * parameters.
1304         *
1305         * <p>Example parameters:
1306         *
1307         * <pre>
1308         * response_type = code
1309         * client_id     = s6BhdRkqt3
1310         * state         = xyz
1311         * redirect_uri  = https://client.example.com/cb
1312         * </pre>
1313         *
1314         * @param uri    The URI of the authorisation endpoint. May be
1315         *               {@code null} if the {@link #toHTTPRequest()} method
1316         *               will not be used.
1317         * @param params The parameters. Must not be {@code null}.
1318         *
1319         * @return The authorisation request.
1320         *
1321         * @throws ParseException If the parameters couldn't be parsed to an
1322         *                        authorisation request.
1323         */
1324        public static AuthorizationRequest parse(final URI uri, final Map<String,List<String>> params)
1325                throws ParseException {
1326                
1327                // Parse response_mode, response_type, client_id, redirect_uri and state first,
1328                // needed if parsing results in a error response
1329                ClientID clientID = null;
1330                URI redirectURI = null;
1331                State state = State.parse(MultivaluedMapUtils.getFirstValue(params, "state"));
1332                ResponseMode rm = null;
1333                ResponseType rt = null;
1334                
1335                // Optional response_mode
1336                String v = MultivaluedMapUtils.getFirstValue(params, "response_mode");
1337                if (StringUtils.isNotBlank(v)) {
1338                        rm = new ResponseMode(v);
1339                }
1340                
1341                // Mandatory client_id, unless in JAR
1342                v = MultivaluedMapUtils.getFirstValue(params, "client_id");
1343                if (StringUtils.isNotBlank(v))
1344                        clientID = new ClientID(v);
1345                
1346                // Optional redirect_uri
1347                v = MultivaluedMapUtils.getFirstValue(params, "redirect_uri");
1348                if (StringUtils.isNotBlank(v)) {
1349                        try {
1350                                redirectURI = new URI(v);
1351                        } catch (URISyntaxException e) {
1352                                // No automatic redirection https://tools.ietf.org/html/rfc6749#section-4.1.2.1
1353                                String msg = "Invalid \"redirect_uri\" parameter: " + e.getMessage();
1354                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1355                        }
1356                }
1357                
1358                // Mandatory response_type, unless in JAR
1359                v = MultivaluedMapUtils.getFirstValue(params, "response_type");
1360                if (StringUtils.isNotBlank(v)) {
1361                        try {
1362                                rt = ResponseType.parse(v);
1363                        } catch (ParseException e) {
1364                                // Only cause
1365                                String msg = "Invalid \"response_type\" parameter";
1366                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1367                                        clientID, redirectURI, rm, state, e);
1368                        }
1369                }
1370                
1371                // Check for a JAR in request or request_uri parameters
1372                v = MultivaluedMapUtils.getFirstValue(params, "request_uri");
1373                URI requestURI = null;
1374                if (StringUtils.isNotBlank(v)) {
1375                        try {
1376                                requestURI = new URI(v);
1377                        } catch (URISyntaxException e) {
1378                                String msg = "Invalid \"request_uri\" parameter: " + e.getMessage();
1379                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1380                                        clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1381                        }
1382                }
1383                
1384                v = MultivaluedMapUtils.getFirstValue(params, "request");
1385                
1386                JWT requestObject = null;
1387                
1388                if (StringUtils.isNotBlank(v)) {
1389                        
1390                        // request_object and request_uri must not be present at the same time
1391                        if (requestURI != null) {
1392                                String msg = "Invalid request: Found mutually exclusive \"request\" and \"request_uri\" parameters";
1393                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1394                                        clientID, redirectURI, ResponseMode.resolve(rm, rt), state, null);
1395                        }
1396                        
1397                        try {
1398                                requestObject = JWTParser.parse(v);
1399                                
1400                        } catch (java.text.ParseException e) {
1401                                String msg = "Invalid \"request_object\" parameter: " + e.getMessage();
1402                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1403                                        clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1404                        }
1405                }
1406
1407                // Client ID mandatory, unless in JAR
1408                if (clientID == null && requestObject == null && requestURI == null) {
1409                        // No automatic redirection https://tools.ietf.org/html/rfc6749#section-4.1.2.1
1410                        String msg = "Missing \"client_id\" parameter";
1411                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1412                }
1413
1414                // Response type mandatory, unless in JAR
1415                if (rt == null && requestObject == null && requestURI == null) {
1416                        String msg = "Missing \"response_type\" parameter";
1417                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1418                                clientID, redirectURI, ResponseMode.resolve(rm, null), state, null);
1419                }
1420
1421
1422                // Parse optional scope
1423                v = MultivaluedMapUtils.getFirstValue(params, "scope");
1424
1425                Scope scope = null;
1426
1427                if (StringUtils.isNotBlank(v))
1428                        scope = Scope.parse(v);
1429
1430
1431                // Parse optional code challenge and method for PKCE
1432                CodeChallenge codeChallenge = null;
1433                CodeChallengeMethod codeChallengeMethod = null;
1434
1435                v = MultivaluedMapUtils.getFirstValue(params, "code_challenge");
1436
1437                if (StringUtils.isNotBlank(v))
1438                        codeChallenge = CodeChallenge.parse(v);
1439
1440                if (codeChallenge != null) {
1441
1442                        v = MultivaluedMapUtils.getFirstValue(params, "code_challenge_method");
1443
1444                        if (StringUtils.isNotBlank(v))
1445                                codeChallengeMethod = CodeChallengeMethod.parse(v);
1446                }
1447                
1448                List<URI> resources = null;
1449                
1450                List<String> vList = params.get("resource");
1451                
1452                if (vList != null) {
1453                        
1454                        resources = new LinkedList<>();
1455                        
1456                        for (String uriValue: vList) {
1457                                
1458                                if (uriValue == null)
1459                                        continue;
1460                                
1461                                String errMsg = "Invalid \"resource\" parameter: Must be an absolute URI and with no query or fragment: " + uriValue;
1462                                
1463                                URI resourceURI;
1464                                try {
1465                                        resourceURI = new URI(uriValue);
1466                                } catch (URISyntaxException e) {
1467                                        throw new ParseException(errMsg, OAuth2Error.INVALID_RESOURCE.setDescription(errMsg),
1468                                                clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1469                                }
1470                                
1471                                if (! ResourceUtils.isValidResourceURI(resourceURI)) {
1472                                        throw new ParseException(errMsg, OAuth2Error.INVALID_RESOURCE.setDescription(errMsg),
1473                                                clientID, redirectURI, ResponseMode.resolve(rm, rt), state, null);
1474                                }
1475                                
1476                                resources.add(resourceURI);
1477                        }
1478                }
1479                
1480                boolean includeGrantedScopes = false;
1481                v = MultivaluedMapUtils.getFirstValue(params, "include_granted_scopes");
1482                if ("true".equals(v)) {
1483                        includeGrantedScopes = true;
1484                }
1485                
1486                Prompt prompt;
1487                try {
1488                        prompt = Prompt.parse(MultivaluedMapUtils.getFirstValue(params, "prompt"));
1489                        
1490                } catch (ParseException e) {
1491                        String msg = "Invalid \"prompt\" parameter: " + e.getMessage();
1492                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg),
1493                                clientID, redirectURI, ResponseMode.resolve(rm, rt), state, e);
1494                }
1495                
1496                // Parse custom parameters
1497                Map<String,List<String>> customParams = null;
1498
1499                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1500
1501                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) {
1502                                // We have a custom parameter
1503                                if (customParams == null) {
1504                                        customParams = new HashMap<>();
1505                                }
1506                                customParams.put(p.getKey(), p.getValue());
1507                        }
1508                }
1509
1510
1511                return new AuthorizationRequest(uri, rt, rm, clientID, redirectURI, scope, state,
1512                        codeChallenge, codeChallengeMethod, resources, includeGrantedScopes,
1513                        requestObject, requestURI,
1514                        prompt,
1515                        customParams);
1516        }
1517
1518
1519        /**
1520         * Parses an authorisation request from the specified URI query string.
1521         *
1522         * <p>Example URI query string:
1523         *
1524         * <pre>
1525         * response_type=code
1526         * &amp;client_id=s6BhdRkqt3
1527         * &amp;state=xyz
1528         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1529         * </pre>
1530         *
1531         * @param query The URI query string. Must not be {@code null}.
1532         *
1533         * @return The authorisation request.
1534         *
1535         * @throws ParseException If the query string couldn't be parsed to an
1536         *                        authorisation request.
1537         */
1538        public static AuthorizationRequest parse(final String query)
1539                throws ParseException {
1540
1541                return parse(null, URLUtils.parseParameters(query));
1542        }
1543        
1544        
1545        /**
1546         * Parses an authorisation request from the specified URI and query
1547         * string.
1548         *
1549         * <p>Example URI query string:
1550         *
1551         * <pre>
1552         * response_type=code
1553         * &amp;client_id=s6BhdRkqt3
1554         * &amp;state=xyz
1555         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1556         * </pre>
1557         *
1558         * @param uri   The URI of the authorisation endpoint. May be 
1559         *              {@code null} if the {@link #toHTTPRequest()} method
1560         *              will not be used.
1561         * @param query The URI query string. Must not be {@code null}.
1562         *
1563         * @return The authorisation request.
1564         *
1565         * @throws ParseException If the query string couldn't be parsed to an 
1566         *                        authorisation request.
1567         */
1568        public static AuthorizationRequest parse(final URI uri, final String query)
1569                throws ParseException {
1570        
1571                return parse(uri, URLUtils.parseParameters(query));
1572        }
1573
1574
1575        /**
1576         * Parses an authorisation request from the specified URI.
1577         *
1578         * <p>Example URI:
1579         *
1580         * <pre>
1581         * https://server.example.com/authorize?
1582         * response_type=code
1583         * &amp;client_id=s6BhdRkqt3
1584         * &amp;state=xyz
1585         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1586         * </pre>
1587         *
1588         * @param uri The URI. Must not be {@code null}.
1589         *
1590         * @return The authorisation request.
1591         *
1592         * @throws ParseException If the URI couldn't be parsed to an
1593         *                        authorisation request.
1594         */
1595        public static AuthorizationRequest parse(final URI uri)
1596                throws ParseException {
1597
1598                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
1599        }
1600        
1601        
1602        /**
1603         * Parses an authorisation request from the specified HTTP request.
1604         *
1605         * <p>Example HTTP request (GET):
1606         *
1607         * <pre>
1608         * https://server.example.com/authorize?
1609         * response_type=code
1610         * &amp;client_id=s6BhdRkqt3
1611         * &amp;state=xyz
1612         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
1613         * </pre>
1614         *
1615         * @param httpRequest The HTTP request. Must not be {@code null}.
1616         *
1617         * @return The authorisation request.
1618         *
1619         * @throws ParseException If the HTTP request couldn't be parsed to an 
1620         *                        authorisation request.
1621         */
1622        public static AuthorizationRequest parse(final HTTPRequest httpRequest) 
1623                throws ParseException {
1624                
1625                String query = httpRequest.getQuery();
1626                
1627                if (query == null)
1628                        throw new ParseException("Missing URI query string");
1629
1630                try {
1631                        return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query);
1632
1633                } catch (URISyntaxException e) {
1634
1635                        throw new ParseException(e.getMessage(), e);
1636                }
1637        }
1638}