www.gusucode.com > external 工具箱matlab源码程序 > external/interfaces/webservices/http/+matlab/+net/+http/Credentials.m

    classdef (Sealed) Credentials < handle & matlab.mixin.Copyable & matlab.mixin.CustomDisplay
    % Credentials Credentials for authenticating HTTP requests
    %   You may include a vector of these Credentials in the HTTPOptions.Credentials
    %   property to specify authentication credentials when sending a RequestMessage
    %   to servers that may require authenticaiton.  The RequestMessage.send method
    %   uses these credentials to respond to authentication challenges from servers
    %   or proxies.  The authentication challenge from the server or proxy is
    %   contained in an AuthenticateField (with the name 'WWW-Authanticate' or
    %   'Proxy-Authenticate') and specifies one or more AuthenticationSchemes that
    %   the server or proxy is willing to accept to satisfy the request.
    %
    %   Exact behavior depends on the AuthenticationScheme, but in general MATLAB
    %   searches the vector of Credentials for one that applies to the request URI
    %   and which supports the specified AuthenticationScheme, and resends the
    %   original request with appropriate credentials in an AuthorizationField
    %   header.  If multiple Credentials apply, the "most specific" one for the
    %   strongest scheme is used. If there are duplicates, the first one is used.
    %
    %   Currently MATLAB implements only the Basic and Digest schemes.  If the server
    %   requires other schemes, or you don't supply Credentials for the required
    %   scheme, you will receive the authentication response message (which will have
    %   a StatusCode of 401 or 407) and must implement the appropriate response
    %   yourself.
    %
    %   Once MATLAB carries out a successful authentication using a Credentials
    %   object, MATLAB saves the results in this Credentials object so that it will
    %   proactively apply these credentials on subsequent requests without waiting
    %   for an authentication challenge from the server.  To take advantage of this,
    %   provide the same Credentials instance on subsequent requests, in the same
    %   or other HTTPOptions objects.
    %
    %   This object is a handle, as it internally accumulates information about prior
    %   (successful) authentications, so that the information can be reused for
    %   subsequent messages.  If you insert this object into multiple HTTPOptions, it
    %   may be updated on each use.  You may copy the object using the copy method,
    %   but that only copies the visible properties that you have set, not the
    %   internal state.
    %
    %   Credentials properties:
    %      Scheme            - vector of AuthenticationScheme to which Credentials applies
    %      Scope             - vector of URI to which Credentials applies
    %      Realm             - vector of string, the realm(s) to which this Credentials applies
    %      Username          - string, the username to use for authentication
    %      Password          - string, the password to use for authentication
    %      GetCredentialsFcn - function handle, to obtain username and password
    %                          without embedding them in this object
    %
    %   Example:
    %
    %      % insure these credentials sent only to appropriate server
    %      scope = URI('http://my.server.com');
    %      creds = Credentials('Username','John','Password','secret','Scope',scope);
    %      options = HTTPOptions('Credentials',creds);
    %      % if the server requires authentication, the following transaction will 
    %      % involve an exchange of several messages
    %      resp = RequestMessage().send(scope, options);
    %      ...
    %      % later, reuse same options that contains same credentials
    %      % since credentials already used successfully, this transaction will only
    %      % require a single message
    %      resp = RequestMessage().send(scope, options)
    %
    % See also HTTPOptions.Credentials, AuthenticationScheme, RequestMessage
    % StatusCode
    
    % Copyright 2015-2016 The MathWorks, Inc.
    properties
        % Scheme - vector of AuthenticationScheme to which the credentials apply
        %   Default is [AuthenticationScheme.Basic, AuthenticationScheme.Digest].  If
        %   empty, it applies to all AuthenticationSchemes.  
        %
        %   If you set this to Basic only, these credentials may be automatically
        %   applied to a request (in an AuthorizationField) whether or not the server
        %   requests authentication.  This avoids an extra round trip responding to
        %   an authentication challenge (since Basic does not require a challenge),
        %   but could be undesirable if you are not sure whether the server requires
        %   Basic authentication, as it exposes the Username and Password to the
        %   server in all cases.
        %
        %   If Digest one of the listed options (or if this property is empty), the
        %   first message to which these Credentials potentially apply (based on
        %   Scope and the request URI) will be sent without an Authorization header
        %   field: these Credentials will be used only if the server responds with a
        %   challenge (providing, of course, that the Scope and Realm match the URI
        %   and the server's challenge).
        %
        %   See also AuthenticationScheme, Scope, Realm, matlab.net.URI
        Scheme matlab.net.http.AuthenticationScheme = ...
                 [matlab.net.http.AuthenticationScheme.Basic, ...
                  matlab.net.http.AuthenticationScheme.Digest]
        
        % Scope - vector of URI or strings to which the credentials apply
        %   The strings must be acceptable to the URI constructor, or of the form
        %   "host/path/..."  Values in this vector are compared against the URI in the
        %   request to determine whether this Credentials object applies.  Normally
        %   this Credentials applies if the request URI refers to the same host at a
        %   path at or deeper than one of the URIs in this Scope. For example a Scope
        %   containing URI naming a host, with no path, applies to request URIs for
        %   all paths on that host.
        %
        %   Only the Host, Port and Path portions of the Scope URIs are used.
        %   Typically you would just specify a Host name, such as
        %   'www.mathworks.com', but you can include a Path, or portion of one, if
        %   you know that the credentials are needed only for some paths within that
        %   Host.  A Host of 'mathworks.com' would match a request to
        %   'www.mathworks.com' as well as 'anything.mathworks.com'.  A URI of
        %   'mathworks.com/foo/bar' would match a request to
        %   'www.mathworks.com/foo/bar/baz' but not to 'www.mathworks.com/foo'
        %   because the latter has a path '/foo' that is not at or deeper than
        %   '/foo/bar'.
        %
        %   An empty Scope (default), or an empty Host or Path in this vector matches
        %   all Hosts or Paths.  You should not leave this property empty if Scheme
        %   is set to Basic only, unless you are careful to send your requests only
        %   to trusted servers, as this would send your Username and Password to any
        %   servers you access using the HTTPOptions containing this Credentials
        %   object.
        %
        %   See also matlab.net.URI, AuthenticationScheme
        Scope
        
        % Realm - vector of regular expressions describing realms for credentials
        %   This may be a string array, character vector, or cell array of character
        %   vectors.  A realm is a string specified by the server in an
        %   AuthenticateField that is intended to be displayed to the user, so the
        %   user knows what name and password to use.  It is useful when a given
        %   server requires different logins for different URIs.
        %
        %   The realm expressions in this list are compared against the
        %   authentication realm in the server's authentication challenge, to
        %   determine whether this Credentials object applies.  Once MATLAB carries
        %   out a successful authentication using one of these realms, MATLAB will
        %   proactively apply this Credentials object to subsequent requests (using
        %   this same Credentials object) to the same Host and Path in the request
        %   URI without requiring another authentication challenge from the server,
        %   or a call to GetCredentialsFcn, on every request.
        %
        %   If you want to anchor the regular expression the start or end of the
        %   authentication realm string, include the '^' or '$' as appropriate.
        %
        %   If this property is [] it is considered to match all realms.  If any
        %   value is an empty string, it only matches an empty or unspecified realm.
        %
        %   In general you would leave this property empty.  Use it only if you want
        %   to specify different Credentials for different realms on the same server
        %   and are not prompting the user for a name and password.
        %
        %   See also regexp, AuthenticationScheme, GetCredentialsFcn
        Realm string
        
        % Username - user name for Basic or Digest authentication schemes
        %   If you set this and the Password property to any string (including an
        %   empty one) this user name will be used for authentication of any request
        %   for which this Credentials object applies, unless GetCredentialsFcn is
        %   specified.  If you set this to [] then you must specify a
        %   GetCredentialsFcn or authentication will not be attempted.  
        %
        %   See also GetCredentialsFcn, AuthenticationScheme
        Username string
        
        % Password - password for Basic or Digest authentication schemes
        %   If you set this and the Username properties are any string (including an
        %   empty one) this password will be used for authentication to any request
        %   for which this Credentials object applies, unless GetCredentialsFcn is
        %   specified.  If you set this to [] then no password will be provided.
        %
        %   See also GetCredentialsFcn, AuthenticationScheme
        Password string
        
        % GetCredentialsFcn - handle to function providing username and password
        %  If you set this property, this function will be called to obtain the
        %  username and password to use for the authentication response, even if the
        %  Username or Password properties in this Credentials object are set.  This
        %  function must take 4-6 input arguments and return at least 2 outputs, with
        %  the following signature:
        %
        %  [username,password] = GetCredentialsFcn(cred,request,response,authInfo,...
        %                                          previousUsername,previousPassword)
        %
        %     cred      handle to this Credentials object
        %     request   the last sent RequestMessage that provoked this
        %               authentication request.  
        %     response  the ResponseMessage from the server containing an
        %               AuthenticateField.  May be empty if this function is being
        %               called prior to getting a response (possible if this
        %               cred.Scheme specifies only Basic).
        %     authInfo  (optional) one element in the vector of AuthInfo returned by 
        %               AuthenticateField.convert() that MATLAB has selected to match
        %               with this Credentials object.  Each object in this array will
        %               have at least Scheme and realm fields.  If you have no use
        %               for this information you needn't specify this argument.
        %     previousUsername, previousPassword (optional)
        %               Initially empty.  If set, these are the values the
        %               GetCredentialsFcn returned in a previous invocation, which
        %               were not accepted by the server.   If you are not prompting
        %               the user for credentials, you should compare these values to
        %               the ones you plan to return.  If they are the same, return []
        %               for the username to indicate that we should give up with an
        %               authentication failure.  If you are prompting the user for
        %               credentials you needn't specify these arguments.
        %     username  the username to use.  It may be '' or string(''), to indicate
        %               the username should be left empty (some servers may require
        %               only a password, not a username), but if [] this says we
        %               should give up and abort the authentication.
        %     password  the password to use.
        %
        %  By implementing this function and leaving the Username and/or Password in
        %  this Credentials empty, you can implement a prompt or other mechanism to
        %  obtain these values from the user without embedding them in your program.
        %  In your prompt, you may want to display the URI of the request and/or the
        %  realm from authInfo.  A convenient pattern may be to set the Username in
        %  the Credentials object and prompt only for the password.  Your prompt can
        %  display that existing Username (or the previousUsername, if set) and give
        %  the user the option to change it.
        %  
        %  The function can examine this Credentials object (the cred argument) as
        %  well as header fields in the request and response to determine which
        %  resource is being accessed, so it can prompt the user for the correct
        %  credentials.  In general the prompt should display authInfo.realm to let
        %  the user know the context of the authentication.
        %
        %  Since the cred parameter is a handle, this function can store the desired
        %  username and password in this object, so that they will be reused for
        %  future requests without invoking the function again.  Usually this is not
        %  necessary, as MATLAB already saves the username and password internally so
        %  it can apply them to future requests.  But MATLAB may not always be able
        %  to determine whether the same username and password apply to different
        %  requests using this Credentials object.
        %
        %  If the function returns [] (as opposed to an empty string) for the
        %  username, this means that authentication is denied and MATLAB returns the
        %  server's authentication failure response message to the caller of send.
        %  This is appropriate behavior if you are implementing a user prompt and the
        %  user clicks cancel in the prompt.  If you are supplying the username and
        %  password programmatically rather than propmting the user, you must return
        %  [] if the previousUsername and previousPassword arguments passed in are
        %  identical to the username and password that you would return (thus
        %  indicating that your credentials are not being accepted and you have no
        %  alternative choice). Otherwise, an infinte loop might occur calling your
        %  GetCredentaislFcn repeatedly.
        %
        %  This is an example of a simple GetCredentialsFcn that prompts the user,
        %  that fills in the Username from the Credentials object as a default:
        %
        %    function [u,p] = getMyCredentials(cred, req, resp, authInfo)
        %        u = cred.Username;
        %        prompt{1} = 'Username:';
        %        prompt{2} = 'Password:';
        %        defAns = {char(u), ''};
        %        title = ['Credentials needed for ' char(authInfo.realm)];
        %        answer = inputdlg(prompt, title, [1, 60], defAns, 'on');
        %        if isempty(answer)
        %            u = [];
        %            p = [];
        %        else
        %            u = answer{1};
        %            p = answer{2};
        %        end
        %    end
        %
        %  The above function prevents the password from being stored in any 
        %  accessible property.
        %
        %  See also RequestMessage, matlab.net.http.field.AuthenticateField, AuthInfo
        GetCredentialsFcn 
    end
    
    properties (Access=private)
        % CredentialInfos - Vector of matlab.internal.webservices.CredentialInfo 
        %   created or updated by updateCredential() containing information needed to
        %   authenticate subsequent requests using this Credentials object without
        %   waiting for an authentication challenge from the server.  On each new
        %   request we first search all CredentialInfos in all Credentials objects to
        %   see if any one applies to the request URI.  If we find none, we search
        %   the Credentials objects to look for the most specific match.  If we find
        %   no matching Credentials object we send the request without
        %   authentication.  In this case a response containing an authentication
        %   challenge (401 or 407) can't be answered and we'll return the challenge
        %   to the user.
        %
        %   If we don't find a CredentialInfo (which means we never authenticated to
        %   this URI), and the most specific matching Credentials object specifies
        %   only AuthenticationScheme.Basic, we send those credentials with the
        %   request.  If the most specific matching Credentials specifies a scheme
        %   other than Basic, or its Scheme is empty, we send the request without
        %   credentials.  If the server comes back with an authentication challenge
        %   (saved as AuthInfo), we once again look for the most specific matching
        %   Credentials object (this time using information in the challenge such as
        %   Scheme and Realm, as well as the URI) and use its credentials to respond
        %   to the challenge.
        %   
        %   When an authentication is successful, we add a new CredentialInfo object
        %   to this array in Credentials object from which we obtained the
        %   credentials.
        %
        %   If we find a CredentialInfo object in that matches the request URI, which
        %   means we previously authenticated using that CredentialInfo, MATLAB will
        %   update the CredentialInfo and send its credentials with the request,
        %   without waiting for a challenge.  If authentication fails (i.e., we get a
        %   challenge anyway), and the URI in the CredentialInfo is identical to that
        %   in the request, we delete the CredentialInfo and retry the request as if
        %   there had been no CredentialInfo.  If the URI is not an exact match, or
        %   if we deleted the CredentialInfo, we go back to the paragraph above as if
        %   we never authenticated to this URI.
        %
        %   We never remove an entry from this list unless its URI exactly matches
        %   that of a request whose authentication has failed.  For example, say we
        %   add an entry for www.mathworks.com/foo.  If a subsequent request is for
        %   www.mathworks.com/foo/bar, we try to use the existing entry.  If it
        %   succeeds, we just update the existing entry.  If it fails, we
        %   authenticate from scratch and if that succeeds, we add a new entry for
        %   www.mathworks.com/foo/bar and keep both.  If a subsequent request comes
        %   in for www.mathworks.com/foo/baz, we again try to use the first entry, as
        %   it's the only one that matches.  If authentication with that entry fails,
        %   we again start from scratch and add a 3rd entry for
        %   www.mathworks.com/foo/baz.  
        %
        %   This property is not copied by the copy() method.
        %
        CredentialInfos = matlab.net.http.internal.CredentialInfo.empty
    end
    
    
    methods
        function obj = Credentials(varargin)
        %Credentials constructor
        %   CREDENTIALS = Credentials(Name,Value) returns a Credentials object
        %   with named properties initialized to the specified values.  Unnamed
        %   properties get their default values, if any.
        
        % Undocumented behavior: allow a single argument that is a Credentials array
        % which returns handle to same array, or [] which returns empty array.
            if nargin ~= 0
                arg = varargin{1};
                if isempty(arg) && isnumeric(arg)
                    obj = matlab.net.http.Credentials.empty;
                else
                    if nargin == 1 && isa(arg,class(obj))
                        obj = arg;
                    else
                        obj = matlab.net.internal.copyParamsToProps(obj, varargin);
                    end
                end
            end
        end
        
        function set.Scope(obj, value)
            import matlab.net.internal.*
            if isempty(value)
                obj.Scope = matlab.net.URI.empty;
            elseif isa(value, 'matlab.net.URI')
                obj.Scope = value;
            else
                value = getStringVector(value, mfilename, 'Scope');
                res = arrayfun(@matlab.net.URI.assumeHost, value, 'UniformOutput', false);
                obj.Scope = [res{:}];
            end
        end
       
        function set.Realm(obj, value)
            import matlab.net.internal.*
            if isempty(value)
                obj.Realm = [];
            else
                obj.Realm = getStringVector(value, mfilename, 'Realm');
            end
        end
        
        function set.Scheme(obj, value)
            import matlab.net.internal.*
            if isempty(value)
                obj.Scheme = [];
            elseif isa(value, 'matlab.net.http.AuthenticationScheme')
                obj.Scheme = value;
            else
                value = getStringVector(value, mfilename, 'Scheme', true, ...
                                        'matlab.net.http.AuthenticationScheme');
                obj.Scheme = matlab.net.http.AuthenticationScheme(value);
            end
        end
        
        function set.Username(obj, value)
            import matlab.net.internal.*
            if ~isempty(value)
                value = getString(value, mfilename, 'Username');
            else
            end
            obj.Username = value;
        end
        
        function set.Password(obj, value)
            import matlab.net.internal.*
            if ~isempty(value)
                value = getString(value, mfilename, 'Password');
            else
            end
            obj.Password = value;
        end
        
        function set.GetCredentialsFcn(obj, value)
            if ~isempty(value)
                validateattributes(value, {'function_handle'}, {'scalar'}, ...
                                   mfilename, 'GetCredentialsFcn');
                if (nargin(value) >= 0 && nargin(value) < 3) || ...
                        (nargout(value) >= 0 && nargout(value) < 2)
                    error(message('MATLAB:http:GetCredentialsFcnError', ...
                        nargin(value), nargout(value), 3, 1));
                else
                end
            else
            end
            obj.GetCredentialsFcn = value;
        end
    end
    
    methods (Access={?matlab.net.http.RequestMessage,?tHTTPCredentialsUnit})
        function cred = getCredentials(obj, uri, authInfos)
        % getCredentials - get Credentials object in a vector of Credentials objects, 
        %   choosing the one with the best match for the strongest scheme.
        %
        %   obj       array of Credentials
        %   uri       URI of request
        %   authInfos vector of AuthInfos (challenges) in the response message; empty 
        %             if no challenge received yet, in which case we choose the best
        %             matching credential based on URI and scheme only
        %   cred      the Credentials object that was matched (it is a handle)
        %
        % Returns [] if there is no match.
        %
        % This function is designed to be called in 2 cases
        %   1. After an "unauthorized" response from a server indicating that
        %      authentication failed or was required.  The uri is the URI of the
        %      request message and the authInfos is information in the server's
        %      response message containing the WWW-Authenticate or Proxy-Authenticate
        %      AuthenticationField contents.  In this case we search the Credentials 
        %      in obj array for a match of uri and authInfos and return the cred.
        %   2. Before any send other than a retry after an "unauthorized" response.
        %      This is to determine if we have previously successfully authenticated
        %      with the server or proxy to be contacted, using one of the Credentials
        %      objects in obj, or to determine whether to proactively send
        %      credentials in a request using Basic authentication.  In this case the
        %      uri is the URI of the request and authInfo is empty.  
        %
        % Matching algorithm looks at these matches, where an empty value on the
        % right matches anything on the left, and an empty value on the left only
        % matches an empty value on the right.  All these have to match in order for
        % a Credential to be selected.
        %
        %      authInfo.Scheme in obj.Scheme (if authInfo specified)
        %      uri.Host        == obj.Scope.Host, anchored to end of uri.Host
        %      uri.Path        == obj.Scope.Path, anchored to start of uri.Path
        %    if authInfo set:
        %      authInfo.Realm  == obj.Realm, general regexp
        %    
        % In case of multiple matches, the most specific (highest priority) match in
        % the first field above wins.  If there is more then one most specific match
        % in the first matching field, then the most specific in the next field down
        % wins, etc. By "most specific" we mean "longest", except that a match with
        % an empty on the right is considered least specific.  If there are multiple
        % equally specific matches, we return the one with the strongest Scheme
        %
        % If authInfo is a vector naming different Schemes, do the above search
        % first with the strongest Scheme (Digest > Basic).
        %
        % For example, for the uri www.internal.mathworks.com/foo/bar the following
        % Scopes match, from most specific to least:
        %
        %                                  uri.Host==Scope.Host  uri.Path==Scope.Path
        %    internal.mathworks.com/foo/bar       full                 full
        %    internal.mathworks.com/foo           full                 partial
        %    internal.mathworks.com               full                 empty
        %    mathworks.com/foo/bar                partial              full
        %    mathworks.com/foo                    partial              partial
        %    mathwork s.com                       partial              empty
        %    /foo/bar                             empty                full
        %    /foo                                 empty                partial
        %    empty                                empty                empty
        
            import matlab.net.http.*
            if isempty(authInfos)
                cred = getCredentialsInternal(obj, uri, authInfos, []);
            else
                cred = getCredentialsInternal(obj, uri, ...
                                            authInfos, AuthenticationScheme.Digest);
                if isempty(cred)
                    cred = getCredentialsInternal(obj, uri, ...
                                            authInfos, AuthenticationScheme.Basic);
                else
                end
            end
        end
        
        % addProxyCredInfo Add candidate proxy Credential info to Credentials
        %   This is used to add a CredentialInfo for a proxy that might require
        %   authentication, whose username and password came from someplace like
        %   preferences, prior to getting a challenge from a server.  This
        %   CredentialInfo has an empty AuthInfo, which we set when choosing this
        %   in response to a challenge. 
        function addProxyCredInfo(obj, proxyURI, username, password)
            credInfo = matlab.net.http.internal.CredentialInfo([], proxyURI, ...
                                                          username, password, true);
            obj.addCredInfo(credInfo, false);
        end
        
        function credInfo = createCredInfo(obj, uri, req, resp, authInfos, ...
                                           forProxy, prevCredInfo)
        % createCredInfo Create a new CredentialInfo as a candidate to be
        %   added to this Credentials object, choosing the strongest Scheme supported
        %   by both this object and authInfos, that has a matching Realm.  Caller
        %   should try to authenticate using this returned credInfo, and if
        %   successful, caller should call addCredInfo(credInfo, true) to add it.
        %
        %   We always call the GetCredentialsFcn, if set.  Otherwise we use the
        %   Username and Password in this object.  In this case, if authentication
        %   using credInfo fails, caller might want to reinvoke this function, in
        %   case the GetCredentialsFcn is interacting with the user and wants to give
        %   the user another chance to type a good name and password.
        %
        %   We don't check that the Scope of this Credentials is appopriate for the
        %   uri -- that is normally done by the caller who chose this Credentials
        %   object.  
        %
        %   prevCredInfo initially empty.  If set, this is the previous credInfo
        %                that we tried to authenticate with but failed.
        %
        %   credInfo   empty if we can't create a CredentialInfo because this
        %              Credentials doesn't allow the Scheme or realm in any
        %              authInfos. Returns the number 0 if we couldn't get either a
        %              username or password because neither were set in this object
        %              and GetCredentialsFcn was unspecified or returned [] for
        %              username.
        
            import matlab.net.http.*
            import matlab.net.http.internal.*
            
            assert(~isempty(authInfos));
            schemeMatch = @(scheme) isempty(obj.Scheme) || ...
                  (any([authInfos.Scheme] == scheme) && any([obj.Scheme] == scheme));
            % look for Scheme match, first Digest and then Basic
            index = find(schemeMatch(AuthenticationScheme.Digest));
            if isempty(index)
                index = find(schemeMatch(AuthenticationScheme.Basic));
                if isempty(index)
                    credInfo = [];
                    return;
                else
                end
            end
            authInfo = authInfos(index);    
            % If we have a Realm, use authInfo only if authInfo.realm matches or is
            % empty
            realm = authInfo.getParameter('realm');
            if ~isempty(obj.Realm) && ~isempty(realm)
                matches = regexp(realm, obj.Realm, 'once');
                if isempty(matches) || (iscell(matches) && ~any([matches{:}]))
                    credInfo = [];
                    return;
                else
                end
            end
                
            if isempty(obj.GetCredentialsFcn)
                username = obj.Username;
                password = obj.Password;
            else
                fcn = obj.GetCredentialsFcn;
                if ~isempty(prevCredInfo)
                    args = {obj, req, resp, authInfo, prevCredInfo.Username, ...
                            prevCredInfo.Password};
                else
                    args = {obj, req, resp, authInfo, [], []};
                end
                % only call fcn with number of args it supports, unless it takes
                % varargin
                fcnArgs = min(nargin(fcn),length(args));
                if fcnArgs >= 0
                    [username, password] = fcn(args{1:fcnArgs});
                else
                    [username, password] = fcn(args{:});
                end
                username = string(username); 
                password = string(password);
            end
            if isempty(username) && ~ischar(username)
                % a username of [], not '' or string('') means don't attempt
                % authentication
                credInfo = 0;
            else
                credInfo = CredentialInfo(authInfo, uri, username, password, forProxy);
            end
        end
        
        function addCredInfo(obj, credInfo, force)
        % addCredInfo Add the credInfo to this object's CredentialInfos
        %   This is called after a successful authentication using credInfo, to add
        %   the credInfo to the CredentialInfos so it can be used again for a future
        %   authentication, or to adjust existing credentials that would work for
        %   this credInfo.  The LastUsed time is updated in any credInfo added or
        %   adjusted.
        %
        %   The force flag applies only to basic.
        %
        %   If credInfo.Scheme is anything other than Basic, or force is true,
        %   unconditionally add the this credInfo to the end of the list.  In the
        %   non-Basic case, this likely means the credInfo was using Digest, and its
        %   URIs is either one URI equal to the URI of the authenticated request that
        %   prompted its creation or its URIs are a vector of absolute URIs
        %   corresponding to all of the URIs in the credInfo.AuthInfo.domain array.
        %   In the Basic/force case, this means that we already tried to proactively
        %   authenticate using all existing CredentialInfos that satisfied the prefix
        %   match, but none worked, or there was no prefix match, so this
        %   new one needs to be added unconditionally, or replace an existing one
        %   with the identical URI and realm.  For example, for /foo/bar we tried to
        %   use an existing /foo, but that failed, so we need to add /foo/bar.  If
        %   there was previously a /foo and /foo/bar that both failed, we'll replace
        %   the existing /foo/bar if its realm matches.
        %
        %   If credInfo.Scheme is Basic and force is false, it means we found an
        %   existing CredentialInfo whose URI matched the prefix of the request URI,
        %   and tried to authenticate with it, but it failed, so we created a new
        %   CredentialInfo which worked.  If that new CredentialInfo has a URI that
        %   exactly matches any existing Basic CredentialInfo, replace the existing
        %   one.  This likely means the username or password changed, which was the
        %   reason for the failure.  If that new one shares a common prefix with an
        %   existing one, with the same realm and username/password it means we could
        %   have used the existing one, but didn't do so because the prefix match
        %   failed. In this case chop the existing one with the longest common prefix
        %   match at the common prefix and don't add the new one.  If there is more
        %   than one such match, use the most recently used.  This handles the case
        %   where an existing /foo/bar has the same credentials as /foo/baz: we trim
        %   the existing one to /foo so that a future reference to /foo/fat will
        %   proactively try to use the same credentials.  (If it fails because the
        %   username or password is different, we'll store the new one for /foo/fat.)
        %   If there is no commo prefix, add the new one.  A common prefix match
        %   includes one with no path at all (i.e., the root).
        
            import matlab.net.http.AuthenticationScheme
            if force || (isempty(credInfo.AuthInfo) || ...
                 credInfo.AuthInfo.Scheme ~= AuthenticationScheme.Basic) || ...
                 isempty(obj.CredentialInfos)
                obj.CredentialInfos(end+1) = credInfo;
            else
                % Basic and not force
                maxCredInfo = []; % handle to best matching CredentialInfo
                maxLength = 0;
                for i = 1 : length(obj.CredentialInfos)
                    % Check if the candidate CredentialInfo matches the one we were
                    % given, with the same username/password and realm.  If so,
                    % remember the one with the longest URI match.
                    testInfo = obj.CredentialInfos(i);
                    if testInfo.AuthInfo.Scheme == AuthenticationScheme.Basic && ...
                       credInfo.appliesTo(testInfo) && ...
                       matchInfoRealms(testInfo.AuthInfo, credInfo.AuthInfo)
                        % Since credInfo and testInfo are Basic, we know URIs has
                        % just one entry
                        len = credInfo.URIs.matchLength(testInfo.URIs);
                        if len > maxLength
                            maxLength = len;
                            maxCredInfo = testInfo;
                        else
                        end
                    else
                    end
                end
                if maxLength > 0
                    % found the best match; trim it down
                    maxCredInfo.chopCommonPrefix(credInfo.URIs);
                    credInfo = maxCredInfo;
                else
                    % Didn't find a match, so add it
                    obj.CredentialInfos(end+1) = credInfo;
                end
            end
            
            % update LastUsed date for this added or modified credInfo
            credInfo.LastUsed = datetime('now');

            function tf = matchInfoRealms(a1, a2)
            % match optional realm fields in two AuthInfos.  If both fields missing,
            % it's a match.
                r1 = a1.getParameter('realm');
                r2 = a2.getParameter('realm');
                tf = (isempty(r1) && isempty(r2)) || isequal(r1,r2);
            end
        end
        
        function credInfos = getCommonPrefixCredInfos(obj, uri)
        % Return all credInfos having a URI that matches everything up to, but not
        % including, Path
            if ~isempty(obj.CredentialInfos)
                credInfos = obj.CredentialInfos.commonPrefix(uri);
            else
                credInfos = [];
            end
        end
        
        function [credRes, credInfoRes] = getBestCredInfo(obj, uri, authInfo, forProxy)
        % getBestCredInfo Find best matching CredentialInfo for URI
        %   Searches across all CredentialInfos obj array of Credentials, returning
        %   the one with the longest match of URI to prefix of uri and the
        %   Credentials object containing it.  In case of tie, returns the one most
        %   recently used.  Prefix match requires all fields through Port to be
        %   exactly the same, and then 0 or more Path segments.
        %
        %   If authInfo is unset, this function is being used to find a
        %   CredentialInfo that we used before, to proactively authenticate without
        %   first getting a challenge. This is appropriate for Basic authentication,
        %   and for Digest authentication after having responded to an earlier
        %   challenge.  In this case, we choose the CredentialInfo with the strongest
        %   AuthInfo.Scheme first and then with longest uri match (but it has to
        %   match at least the host in the uri).
        %
        %   If authInfo is set, this is being called after having received a
        %   challenge.  In this case we only look at CredentialInfos whose AuthInfo
        %   applies to the challenge authInfo.
        %
        %   forProxy   if set, look only in CredentialInfos where ForProxy is set
        %              if unset or missing, look only in CredentialInfos where
        %              ForProxy is not set.
        
        %   Returns the CredentialInfo and its containing Credentials
            matchLen = 0; % length of longest match for strongest scheme
            credRes = [];
            credInfoRes = [];
            maxScheme = -1;  % numeric value of AuthInfo.Scheme we found
            if nargin < 4
                forProxy = false;
            else
            end
                
            for i = 1 : length(obj)
                cred = obj(i);
                for j = 1 : length(cred.CredentialInfos)
                    credInfo = cred.CredentialInfos(j);
                    % get scheme in AuthInfo as an absolute number
                    if ~isempty(credInfo.AuthInfo) 
                        scheme = abs(credInfo.AuthInfo.Scheme);
                    else
                        % only proxy credInfos have no AuthInfo
                        assert(credInfo.ForProxy)
                        scheme = 0;
                    end
                    % If authInfo is set, don't look at Scheme in credInfo;
                    % otherwise look only at strongest Scheme first.  If
                    % credInfo.AuthInfo is empty, use it only if we don't already
                    % have maxScheme.
                    if credInfo.ForProxy == forProxy && ...
                       (isempty(credInfo.AuthInfo) || ...
                          credInfo.AuthInfo.worksFor(authInfo)) && ...
                       (~isempty(authInfo) || ...
                          (isempty(credInfo.AuthInfo) && maxScheme < 0) || ...
                          scheme >= maxScheme)
                        % the credInfo applies to authInfo (or its credInfo.AuthInfo
                        % is empty), or authInfo is empty and this credInfo is the
                        % equal to or stronger than maxScheme
                        for k = 1 : length(credInfo.URIs)
                            testURI = credInfo.URIs(k);
                            len = testURI.matchLength(uri);
                            % Use this credInfo if:
                            %   scheme same as maxScheme: it has a longer match
                            %      or its match is equal and it was more recently
                            %      used
                            %   scheme greater than maxScheme: it matches any part
                            if (scheme == maxScheme && ...
                                (len > matchLen) || ...
                                (len == matchLen && ...
                                 (~isempty(credInfoRes) && ...
                                  credInfo.LastUsed > credInfoRes.LastUsed))) || ...
                               (scheme > maxScheme && len > 0)
                                matchLen = len;
                                credRes = cred;
                                credInfoRes = credInfo;
                                if ~isempty(credInfo.AuthInfo)
                                    maxScheme = scheme;
                                else
                                end
                            end
                        end
                    else
                    end
                end
            end
            if ~isempty(credInfoRes) && isempty(credInfoRes.AuthInfo)
                % If the chosen credInfo doesn't have an AuthInfo, set it to the
                % one we matched.  This only applies if the credInfo was inserted by
                % addProxyCredInfo.
                credInfoRes.AuthInfo = authInfo;
            end
        end
        
        function delete(obj, credInfo)
        % delete the credInfo from this object
            contains = obj.CredentialInfos == credInfo;
            assert(any(contains))
            obj.CredentialInfos(contains) = [];
        end
    end
    
    methods (Access=protected, Hidden)
        function cpObj = copyElement(obj)
        % Overridden to copy everything except the CredentialInfos handles.
            cpObj = copyElement@matlab.mixin.Copyable(obj);
            cpObj.CredentialInfos = matlab.net.http.internal.CredentialInfo.empty;
        end
    end
    
    methods (Access=private)
        function cred = getCredentialsInternal(obj, uri, authInfos, authScheme)
        % Implementation of getCredentials.  If there are challenges (authInfos) only
        % get credentials that match the challenge for the specified authScheme.  If
        % there are no challenges ignore authScheme and get the best matching
        % credentials for the strongest scheme.  We don't use req or resp but pass
        % them into the GetCredendialsFcn if necessary. 
        %
        % Note obj is vector of Credentials
        
        % TBD this function badly needs decomposing.
            cred = [];
            
            % get the one AuthInfo we should look at, if any
            if isempty(authInfos)
                authInfo = [];
            else
                % only look at specified authScheme, and pick only the first matching
                % authInfo
                authInfos = authInfos([authInfos.Scheme] == authScheme);
                % if no authInfo for the authScheme, we're done
                if isempty(authInfos) 
                    return
                else
                end
                authInfo = authInfos(1);
            end
            
            % If authScheme specified, first find best Credentials whose Scheme
            % contains authScheme.  If none, look at Credentials with empty Scheme
            % (which matches anything).  If authScheme not specified, just find best
            % Credentials based on other criteria, picking the one with strongest
            % Scheme only if there's a tie.
            comparator{1} = @(c) ismember(authScheme, [c.Scheme]);
            comparator{2} = @(c) isempty(c.Scheme);
            for ci = 1 : length(comparator)
                if ci > 1 && isempty(authScheme)
                    % 1st comparator done; If authScheme is empty, we already
                    % looked at all creds, so no need to run through 2nd comparator
                    break
                else 
                end
                if ~isempty(authScheme)
                    % If authScheme specified, work only on creds that have matching
                    % schemes with authScheme.  
                    creds = obj(arrayfun(comparator{ci}, obj));
                    if isempty(creds)
                        % None of the creds have a matching scheme
                        continue
                    else % else clause included so that profiler gets to end statement
                    end
                else
                    % No authScheme specified, then look at all creds
                    creds = obj;
                end
                
                % The schemes in all creds match authScheme, or authScheme is empty

                % Get array of credentials matching Scope.Host and uri.Host with the
                % same Port. This will also match ones with empty scope.
                host = uri.Host; 
                port = uri.Port;
                % Lauren's inline conditional: iif(cond1,act1,cond2,act2,...,true,default)
                iif = @(varargin) varargin{2*find([varargin{1:2:end}], 1)}();

                % Function returning the priority of the match of uri.Host to sHost,
                % anchored to end of string.  Priority is:
                %    no match     -1
                %    empty        0
                %    match        number of matching characters
                % The idea is that an empty sHost matches any uri.Host, but gets
                % lower priority than an actual match. In addition, if sPort isn't
                % empty, it has to match port exactly.
                hostMatcher = @(sHost, sPort) iif( ...
                   isempty(sHost),    0, ...
                   (isempty(sPort) || isequal(port,sPort)) && ~isempty(sHost) && ...
                       ~isempty(regexp(host, matlab.net.internal.getSafeRegexp(sHost) + '$','once')), ...
                                          @()strlength(sHost), ... 
                   true,             -1); 
                % Function that takes a scope (array of URIs) and returns the max
                % priority of the match of any of scope.Host fields, based on
                % hostMatcher. Returns 0 if scope is empty.  We need to use {scope.Host}
                % instead of [scope.Host] so that empty values get passed into
                % hostMatcher.
                hostScopeMatcher = @(scope) iif( ...
                    isempty(scope), 0, ...
                    true,           @()max(cellfun(hostMatcher, {scope.Host}, {scope.Port})));
                % Pass in each Scope array to hostScopeMatcher and get priority of
                % each.  Result is a array of numbers. 
                priorities = cellfun(hostScopeMatcher, {creds.Scope});
                
                % Sort creds and matches by priority, high to low
                [~,indices] = sort(priorities);
                indices = fliplr(indices); 
                creds = creds(indices);    
                
                % Now creds is vector of Credentials whose URIs have have a Host and
                % Port that matches the uri Host and Port, and
                % priorities(i) is priority of Host match in creds(i).  It will be
                % something like:
                %   15 15 15 10 10 2 2 2 2 0 -1 -1 -1
                % which says that:
                %   creds(1:3) prioirty 15: match 15 characters
                %   creds(4:5) priority 10: match 10 characters
                %   creds(6:9) priority 4:  match 4 characters
                %   creds(10)  priority 0:  no host specified in creds (matches any)
                %   creds(11:13) don't match
                % and the rest don't match.
                priorities = priorities(indices); 
                path = uri.EncodedPath;

                % Now do Path matching.
                % Go through each block of creds that has an equal value of host
                % priority and choose the one with the longest match of uri.Path to
                % creds.Scope.Path, and then longest realm match.  In the example
                % above we first go through all the 15's looking for the longest Path
                % and realm match, then the 10's, then 2's and finally the 0.
                % We only jump to the next block of priorities if there was no Path
                % and realm match in the previous block.
                blockIndex = 1;
                matchIndex = 0;
                while priorities(blockIndex) >= 0
                    % Work on the block of equal priorities(blockIndex) beginning at
                    % blockIndex: these are all creds with same length of matching
                    % host.
                    matchIndex = 0;      % index of best Scope.Path/Realm in creds so far
                    pathPriority = -2;   % priority of Path match at matchIndex
                    realmPriority = -1;  % priority of Realm match at matchIndex
                    hostPriority = priorities(blockIndex); % Host priority we're working on
                    % Advance to end of block, looking for
                    % match with Path and then Realm
                    for j = blockIndex : length(creds)
                        if priorities(j) ~= hostPriority
                            % j has gotten to the next block
                            assert(priorities(j) <= hostPriority); % expect decreasing
                            break % advance to next block
                        else % else clause included so that profiler gets to end statement
                        end
                        % work on this block of creds with same hostPriority;
                        % first see if creds.Scope.Path matches path
                        scope = creds(j).Scope;
                        % set pathLen to priority of path match: length of longest
                        % match or 0 for any empty match
                        if isempty(scope)
                            pathLen = 0;
                        else
                            pathMatcher = @(sPath) iif( ...
                               sPath=='', 0, ...
                               ~isempty(regexp(path, '^' + matlab.net.internal.getSafeRegexp(sPath),'once')), ...
                                               @()strlength(sPath), ... 
                               true,           -1); 
                            pathLen = max(cellfun(pathMatcher, {scope.EncodedPath}));
                            if pathLen < 0
                                % none of the Paths in this cred match; go to next
                                % cred in block
                                continue 
                            else 
                            end
                            % pathLen is legnth of path match
                        end
                        if pathLen >= pathPriority
                            % a path match with greater or equal priority to the
                            % previous one, then go on to check realm.  Set rlen to
                            % Realm match priority.
                            rlen = matchRealm(creds(j).Realm, iif, authInfo);
                            if rlen < 0
                                continue % alas, none of the Realms match; skip cred
                            else 
                            end
                            % At least one realm matches, with highest priority rlen.
                            % Function returns true if vector a is nonempty and has a
                            % larger value than any in vector b
                            aGTb = @(a,b) ~isempty(a) && ...
                                             (isempty(b) || max(a) > max(b));
                            if pathLen > pathPriority || ...
                               (pathLen == pathPriority && ...
                                (rlen > realmPriority || ...
                                 (rlen == realmPriority && ...
                                  aGTb(creds(j).Scheme, creds(matchIndex).Scheme))))
                                % A path match and it is higher priority as the
                                % previous one; or the same priority and its realm
                                % match is higher; or the realm match is equal but
                                % the scheme is better.  Save it and the priority of
                                % its Realm match.
                                pathPriority = pathLen;
                                matchIndex = j;
                                realmPriority = rlen;
                            else
                            end
                            % if path match is the same but realm match is shorter,
                            % or realm is equal and scheme is not better, ignore and
                            % keep going to next cred
                        else 
                            % if path match is shorter than longest so far, ignore
                            % cred
                        end
                    end
                    % we got to the end of creds in this block
                    if matchIndex > 0 || (matchIndex == 0 && j == length(creds))
                        % we got a match, or there was no match and we got to the end
                        % of all the blocks, so we're done
                        break % we're done
                    else
                        % go to next block
                        blockIndex = j;
                    end
                end
                if matchIndex > 0
                    % we found a match with this comparator
                    cred = creds(matchIndex);
                    break  % we're done
                else
                end
            end % advance to next comparator for authScheme
        end % function getCredentialsInternal
    end % methods(Access=private)
    
    methods (Access = protected)
                
        function group = getPropertyGroups(obj)
        % Provide a custom display for the case in which obj is scalar
        % Display Scheme and Scope as strings.  
        % If Password is non-empty, replace each character with '*'.
            
            group = getPropertyGroups@matlab.mixin.CustomDisplay(obj);
            if isscalar(obj)
                scheme = group.PropertyList.Scheme;
                if ~isscalar(scheme) && ~isempty(scheme)
                    scheme = strjoin(arrayfun(@char,scheme,'UniformOutput',false),...
                                     ', ');
                    group.PropertyList.Scheme = scheme;
                else
                end
                scope = strjoin(arrayfun(@char, group.PropertyList.Scope, ...
                                         'UniformOutput',false),...
                                 ', ');
                group.PropertyList.Scope = scope;
                password = group.PropertyList.Password;
                if ~isempty(password)
                    pw(1:strlength(password)) = '*';
                    group.PropertyList.Password = string(pw);
                else
                end
                % make empty fields look like []
                names = fieldnames(group.PropertyList);
                for i = 1 : length(names)
                    name = names{i};
                    if isempty(group.PropertyList.(name))
                        group.PropertyList.(name) = [];
                    end
                end
            end
        end
    end
    
    methods (Access=?tHTTPCredentialsUnit)
        function credInfos = getCredInfos(obj)
        % Test API only.
            credInfos = obj.CredentialInfos;
        end
    end
end

function rlen = matchRealm(realms, iif, authInfo)
% match array of realms to authInfo, returning priority of longest match or -1
    
    if isempty(realms) || isempty(authInfo) || isempty(authInfo.getParameter('realm'))
        rlen = 0; % empty Realm matches anything, lowest priority
    else
        realm = authInfo.getParameter('realm');
        if isempty(realm)
            rlen = 0;
        else
            match = @(r) regexp(realm, r, 'once');
            % matcher returns first and last of each match, or [0,-1]
            % if no match
            realmMatcher = @(sRealm) iif( ...
                isempty(match(sRealm)),  @()deal(0,-1), ...
                sRealm=='' && realm=='', @()deal(0,0), ...
                true,                    @()match(sRealm));
            [first, last] = arrayfun(realmMatcher, realms);
            rlen = max(last - first);
            if rlen < 0
                % no Realm matches
                rlen = -1;
            end
        end
    end
end