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