www.gusucode.com > external 工具箱matlab源码程序 > external/interfaces/webservices/http/+matlab/+net/+http/HeaderField.m
classdef (AllowedSubclasses={... ?matlab.net.http.field.AuthenticateField, ... ?matlab.net.http.field.AuthenticationInfoField, ... ?matlab.net.http.field.AuthorizationField, ... ?matlab.net.http.field.CaseInsensitiveStringField, ... ?matlab.net.http.field.CookieField, ... ?matlab.net.http.field.GenericField, ... ?matlab.net.http.field.IntegerField, ... ?matlab.net.http.field.LocationField, ... ?matlab.net.http.field.MediaRangeField, ... ?matlab.net.http.field.ScalarDateField, ... ?matlab.net.http.field.SetCookieField, ... ?matlab.net.http.field.URIReferenceField, ... }) HeaderField < matlab.mixin.Heterogeneous % HeaderField Field of an HTTP header % This class implements a header field of an HTTP message, providing conversions % between strings in the header and MATLAB arrays and structs. This field has two % properties, Name and Value, both strings, and in the simplest case these % properties may be set to arbitrary values, subject to constraints on the % characters allowed in HTTP header fields. % % The Name field defines the "type" of this field, and for some commonly used % fields there are subclasses in the matlab.net.http.field package that support % those field types. For example matlab.net.http.field.DateField implements 'Date' % header field, so if you are creating one of those it is better to use that % subclass, which accepts a value in the form of a date-time string or a datetime % object. If, instead, you use HeaderField to create a 'Date' field, any Value you % specify for the field will be interpreted and enforced by the DateField class, % even though this object is not an instance of that class. Likewise, if you use % the convert method to convert the field value to a MATLAB datetime, the % DateField's convert method will be used. % % To see a list of all supported subclasses, use HeaderField.displaySubclasses. % % HeaderField properties: % Name - name of the field (string) % Value - value of the field (string) % % HeaderField methods: % HeaderField - constructor % convert - return Value as MATLAB type % parse - parse Value generically and return strings % eq, == - compare for functional equivalence % isequal - compare for functional equivalence % char, string - return HeaderField as a character vector or string % displaySubclasses - (static) display list of supported subclasses % % See also HeaderField, displaySubclasses, matlab.net.http.field.DateField % The following documentation is for internal use only. It's behavior is likely to % change in a future release. % % Subclass authors: All subclasses must be defined in the matlab.net.http.field % package. Every subclass must implement getSupportedNames() to return the field % name, or cell array of names, that it implements. % % All subclasses should override convert() and one or more of the protected methods % in this class to customize the subclass behavior. For example % getStringException() and scalarToString() should be overridden to check or % convert supplied field values that the caller supplies, to strings that are % stored in the header, and parseField() and/or parseElement() should be % implemented to convert a string in the header to MATLAB data types. % % If a subclass's getSupportedNames() function returns vector of strings, the % subclass must provide a 2-argument constructor taking NAME and VALUE parameters. % If it returns a scalar string it must provide a 1-argument constructor that takes % just a VALUE. % % A subclass constructor should invoke its superclass constructor % with the NAME and VALUE parameters. The subclass need not validate these % parameters in its constructor: the infrastructure will insure that the NAME is % one of those allowed by getSupportedNames(), and will invoke the subclass's % scalarToString() and getStringException() to insure that the VALUE is valid. % Copyright 2015-2016 The MathWorks, Inc. properties (AbortSet) % Name - name of the field (a string) % When you set this property you effectively change the field's type, that % may determine what values of the Value property are legal. % % If this is an instance of a subclass implementing a specific header % field type, it will likely enforce constraints on the name you can set. % If you set this to [] or an empty string, the value will become []. Name % Value - value of the field (a string) % When you read this property you always get a string representing the % value in the field. When you set this property the value may be any % type acceptable to this field based on the Name property and/or the % class of this object, and the result will be converted to a string. For % field types that have a default value, specifying an empty string such % as '' or string('') will insert that default. Specifying an empty % double, [], causes this field not to be added by RequestMessage.send or % RequestMessage.complete when completing the message. Fields with [] % values are not included in the completed message. % % See also Name, parse, convert, RequestMessage Value % For internal use only: Subclass authors may control how values are % validated and converted by overriding various protected methods in this % class. Default behavior, if this is not a subclass, is described in the % valueToString method. end properties (Constant, Hidden) % TokenChars - characters allowed in a token % This is a regular expression that matches all characters allowed in a % token of a header, as defined in RFC 7320, <a href="http://tools.ietf.org/html/rfc7230#section-3.2.6">section 3.2.6</a>. % You can use this to assist in your own parsing of header fields. TokenChars = string('-!#$%&''*+.^`|~a-zA-Z_0-9') end methods function obj = set.Value(obj, value) % If value is an empty string, either '' or "", gets the default value from % getDefaultValue(). Invokes validateValue to check and/or convert the % value. if (isempty(value) && ischar(value)) || ... (isstring(value) && isscalar(value) && value=='') value = obj.getDefaultValue(); end obj.Value = validateValue(obj, obj.Name, value); %#ok<MCSUP> end function obj = set.Name(obj, name) % Verifies that name is in getSupportedNames of this object, and invokes % validateValue to check that the value, if already set, is permitted for % this name. import matlab.net.internal.getString supportedNames = obj.getSupportedNames(); name = strtrim(getString(name, class(obj), 'Name', true)); if name=='' name = []; elseif ~isempty(name) if ~isempty(supportedNames) if ~any(strcmpi(name,supportedNames)) if ischar(supportedNames) supportedNames = {supportedNames}; end error(message('MATLAB:http:BadName', char(name), class(obj), ... char(strjoin(supportedNames)))); % TBD string end elseif ~obj.isBaseClass() && ... ~isa(obj, 'matlab.net.http.field.GenericField') % Class of obj allows any name and it's not this base class or % instance of GenericField. Make sure user doesn't choose a name % implemented by some other class, as that could result in % unintentional bypass of value validation. otherclass = matlab.net.http.internal.PackageMonitor.getClassForName(name); match = @(x) x{1}{1}; nameOf = @(c) match(regexp(c, '\.([^.]+)$','tokens')); if ~isempty(otherclass) && ~(metaclass(obj) >= otherclass) error(message('MATLAB:http:NameUsedByOtherClass', ... nameOf(class(obj)), char(name), nameOf(otherclass.Name))); end end % get any chars illegal in a name bad = regexp(name, '[^' + obj.TokenChars + ']', 'match', 'once'); if ~isempty(bad) && ~ismissing(bad) && bad ~= '' error(message('MATLAB:http:BadCharInProp', ... char(bad), char(name), 'HeaderField.Name')); end validateValue(obj, name, obj.Value); %#ok<MCSUP> end obj.Name = name; end function obj = HeaderField(varargin) % HeaderField - Constructor for header field array % FIELDS = HeaderField(NAME1,VALUE1,...,NAMEn,VALUEn) creates HeaderFields % with the specified names and values, % % NAME - name of the field (a string) % VALUE - value of the field (a string or type appropriate for the NAME) % % Either argument may be []. If the last VALUE is missing it is treated % as empty. % % This constructor creates fields whose class is HeaderField. If you are % creating a field of a class defined in the matlab.net.http.field package, % you should use that class constructor instead. For a list of these % classes, use displaySubclasses. % % For example these two both create a 'Content-Type' field with the same % Name and Value: % % ctf = matlab.net.http.HeaderField('Content-Type','text/plain'); % ctf = matlab.net.http.field.ContentTypeField('text/plain'); % % The second form is preferred because you cannot misspell the field name % (as it is derived from the class name), while the first will accept any % field name and you may not find out about the error until the server % rejects the message. In fact, most servers silently ignore unknown field % names. % % However, assuming the field name in the first case is correct, the same % validation of the VALUE takes place (in this case, that it is a valid % media-type) regardless of which constructor you use. % % If you want to create a particular HeaderField with a Name and Value that % this constructor rejects, use GenericField. % % See also Name, Value, displaySubclasses, % matlab.net.http.field.ContentTypeField, matlab.net.http.field.GenericField if nargin ~= 0 if nargin == 1 && isempty(varargin{1}) % This to support coercion of [] to empty array obj = matlab.net.http.HeaderField.empty; else if mod(nargin,2) > 0 varargin{nargin+1} = []; end max = length(varargin)/2; obj = repmat(obj,1,max); for i = max : -1 : 1 i2 = i*2; obj(i).Name = varargin{i2-1}; obj(i).Value = varargin{i2}; end end end end function value = convert(obj, varargin) % convert Return the value of a header field converted to a MATLAB type % VALUES = convert(FIELDS) returns an array or cell array that is the result % of calling convert on each header field in FIELDs. If there is a custom % class for the field in the matlab.net.http.field package based on the % field's Name, invokes it to convert the Value to a MATLAB value or object. % Throws an exception if the conversion fails or there is no custom class % that supports this Name. This method does not work on heterogeneous % arrays: all members of FIELDS must be the same class. % % VALUES is normally an array of values containing all the results of % converting FIELDS. It is not necessarily the same length as FIELDS % because the convert method for some individual fields may return an array % of values. If all converted values are not of the same type, VALUES is a % cell array of the values. % % See displaySubclasses for the list of fields supporting convert. Use the % parse method to process header fields for which there is no convert % method. % % See also Name, Value, parse, convertLike, displaySubclasses % Subclass authors: you must override this to implement custom parsing of % the header fields you support to return a value of the desired type, or to % convert the value using array or struct delimiters other than the default % (comma and semicolon). Your convert method must not call this convert % method. assert(strcmp(class(obj), 'matlab.net.http.HeaderField')); %#ok<STISA> need to check exact class, not subclass value = convertInternal(obj, [], varargin{:}); end end methods (Sealed) % Methods sealed so that they work on heterogeneous arrays function tf = eq(obj,other) % == Compare two HeaderField arrays % A == B does an element by element comparison of two HeaderField arrays, % returning an array of logicals indicating matching elements. The arrays % must have the same dimensions, unless one is a scalar. % % Two HeaderFields are considered equal if they are functionally % equivalent, even if not identical. This means their Names match using a % case-insensitive compare, and their Values match based on using isequal % on the result of convert(), if convert() is supported for the HeaderField % type. If convert() is not supported, comparisons are based on a % case-sensitive match of the Value strings. % % This comparison only uses the Name and Value properties and ignores the % actual classes of A and B, as long as both are instances of HeaderField. % For example, the last line below evaluates to true, even though one is % a HeaderField and the other is a DateField, because their values are the % same and DateField.Name is always 'Date'. % % import matlab.net.http.HeaderField % dt = datetime('now'); % HeaderField('date', dt) == matlab.net.http.field.DateField(dt) % % See also Name, Value, matlab.net.http.field.DateField, convert tf = isa(obj, 'matlab.net.http.HeaderField') && ... isa(other, 'matlab.net.http.HeaderField'); if tf % compare two strings, where [] == [] is true cmpi = @(a,b) (isempty(a) && isempty(b)) || ... (~isempty(a) && ~isempty(b) && strcmpi(a,b)); % return logical array comparing scalar x to each element of array y fun = @(x,y) arrayfun(@(y) cmpi(x.Name, y.Name) && ... cmpv(x, y), y); % do scalar expansion if one is a scalar and the other not if isscalar(other) tf = fun(other, obj); elseif isscalar(obj) tf = fun(obj, other); elseif ndims(obj) == ndims(other) && all(size(obj) == size(other)) tf = arrayfun(@(x,y) x == y, obj, other); if isempty(tf) % this can occur if obj and other are both HeaderField.empty tf = true; end else error(message('MATLAB:dimagree')); end end function tf = cmpv(a,b) % Compare the values of two HeaderField objects with the same name for % equality. If the Value fields are not equal, use the convert() method % to get the values and compare the result. tf = isempty(a.Value) && isempty(b.Value); if tf, return, end % both empty tf = isempty(a.Value) == isempty(b.Value); if ~tf, return, end % only one empty % both not empty tf = strtrim(a.Value) == strtrim(b.Value); if tf, return, end % strings equal % strings not equal, call convert try c1 = a.convert(); c2 = b.convert(); % do isequal comparison first because it works on any object % type; do == in case object implements scalar expansion tf = isequal(c1,c2) || all(c1 == c2); catch % convert() not supported or couldn't compare using isequal or eq tf = false; end end end function tf = isequal(obj,other) % isequal True if two HeaderField arrays are equal % The arrays must be the same size and corresponding elements must compare % equal according to eq. % % See also eq. tf = isequal(size(obj), size(other)) && all(obj == other); end function value = convertLike(obj, other, varargin) % convertLike Convert the value of a header field like another header field % VALUES = convertLike(FIELDS, OTHER) returns the value of the field % converted to a MATLAB type, using conversion rules of the field OTHER. % OTHER is a string, character vector, HeaderField or meta.class identifying % a custom HeaderField subclass. This performs the same action as convert % but allows you to process the value of any header field as if it were % another header field. % % Use this when you have a header field of a type for which MATLAB does not % have a custom type, but which has content that can be parsed by one of the % custom types. For example, suppose you receive a message that has a % header field with the name 'Created-Date' whose contents is formatted like % the HTTPDateField. Since MATLAB does not recognize 'Created-Date' as a % custom header field, you can write the following to retrieve a datetime % from the value. % % myField = response.getFields('Created-Date'); % date = myField.convertLike(?matlab.net.http.field.HTTPDateField); % % See displaySubclasses for the list of fields supporting convertLike. % % See also convert, displaySubclasses if ~ischar(other) validateattributes(other, {'string','matlab.net.http.HeaderField','meta.class'}, {'scalar'}, 'convertLike', 'OTHER'); end if isa(other, 'matlab.net.http.HeaderField') otherClass = metaclass(other); otherField = other; elseif isa(other, 'meta.class') otherClass = other; if ~(otherClass < ?matlab.net.http.HeaderField) error(message('MATLAB:http:NotHeaderFieldSubclass', otherClass.Name)); end otherField = []; else otherArg = matlab.net.internal.getString(other, 'convertLike', ... 'OTHER', false, {'meta.class','HeaderField'}); validateattributes(char(otherArg), {'char'}, {'nonempty'}, 'convertLike', 'OTHER'); otherField = []; otherClass = matlab.net.http.internal.PackageMonitor.getClassForName(otherArg); if isempty(otherClass) error(message('MATLAB:http:NoCustomConversion', char(otherArg))); end end if isempty(otherField) % if we don't have a name for the other class, need to pick a supported one otherName = matlab.net.http.internal.PackageMonitor.getNamesForClass(otherClass); if isempty(otherName) otherName = 'dummy'; else otherName = otherName(1); end otherField = createField(otherName); assert(~isempty(otherField)); % can't happen because we used supported name end value = convertInternal(obj, otherField, varargin{:}); end function value = convertInternal(obj, field, varargin) % convertInternal Convert obj.Value like field. If field is [], use the rules % of the custom class that implements obj.Name. obj may be an array; returns % vector or cell vector. if isscalar(obj) % if field specified, copy our value into it and request convert % otherwise create a new field using current field's name if ~isempty(field) field.Value = obj.Value; else if isempty(obj.Name) || obj.Name == '' error(message('MATLAB:http:NameEmpty')); end field = createField(obj.Name, obj.Value); if isempty(field) error(message('MATLAB:http:NoCustomConversion', char(obj.Name))); end % The call to convert below will recurse forever if field is the same class as % this object. This can only happen if a subclass of HeaderField calls % HeaderField.convert, which isn't allowed. assert(~strcmp(class(field),class(obj))); end value = field.convert(varargin{:}); elseif isempty(obj) value = []; else convertit = @(o) convertInternal(o, field, varargin{:}); value = arrayfun(convertit, obj, 'UniformOutput', false); try % try putting all values into array; if fails, keep cell array value = [value{:}]; catch end end end end methods (Static, Hidden) function names = getSupportedNames() % getSupportedNames return field names that this class supports % This is intended to be implemented by subclasses to declare the names of % header fields the subclass supports. Return value is a string vector. % % Each time you set the Name or Value field of a HeaderField object % (not a subclass), the set.Name and set.Value methods query this method in % all the subclasses in the matlab.net.http.field package to see which ones % implement the Name. Most specific subclass implementing the Name is % invoked (via a constructor call) to validate the provided Name and Value. % % If getSupportedNames returns just a single string, indicating that the % subclass supports only one Name, the set.Value and set.Name methods of % HeaderField invoke the subclass constructor with just the Value. If it % returns a vector, indicating that the subclass implements multiple names, % the constructor is invoked with both Name and Value arguments. % % This method is also queried when the infrastructure forms a % ReponseMessage received from a server, to populate the Header array in % the ResponseMessage with appropriate subclasses of HeaderField. % % If this method returns [] (the default) it indicates this is a support % class or base class that will not be considered by the infrastructure to % be an implementation of any particular field type. Even though this base % class returns [], you must still override this to return [] if your class % supports any name, or a warning will be thrown. % % Subclasses that extend other subclasses that implement this method should % override this method if they don't implement all the supported names of % their immediate superclass. If they implement the same name as one or % more of their superclasses, the subclass will be used for that name % instead of the superclass. names = []; end end methods (Sealed, Static, Hidden) function field = createHeaderField(varargin) % createHeaderField Create the appropriate HeaderField subclass % FIELD = createHeaderField(NAME,VALUE) create field with NAME and VALUE % This is a factory method to create a new header field. If NAME is not % empty, it looks in the matlab.net.http.field package for a subclass of % matlab.net.http.HeaderField whose getSupportedNames() method returns % NAME. If so, it invokes that subclass's constructor, possibly throwing an % error if VALUE is invalid for that sublcass. If there is no subclass it % invokes the base class HeaderField constructor. NAME must be empty or a % string. The type of VALUE may be any type that the constructor accepts. % % NEWFIELD = createHeaderField(FIELD) create a possibly new field from FIELD % In this usage an existing FIELD is provided. If class(FIELD) is % matlab.net.http.HeaderField, then the Name and Value of FIELD are used as % above to possibly construct a new object of the appropriate subclass. % Otherwise FIELD is simply returned. if nargin == 1 field = varargin{1}; validateattributes(field, {'matlab.net.http.HeaderField'}, ... {'scalar'}, 'createHeaderField'); if ~infield.isBaseClass() return; else name = field.Name; value = field.Value; end else narginchk(2, 2); name = varargin{1}; if ~isempty(name) name = matlab.net.internal.getString(name, ... mfilename,'Name'); end value = varargin{2}; end field = createField(name, value); % may throw exception if isempty(field) field = matlab.net.http.HeaderField(name, value); end end end methods (Access = private) function rval = validateValue(obj, name, value) % name string object or [] % value whatever the user specified % rval string or [] % Called whenever the Name or Value property in this object is set, to % validate and/or convert the value to an appropriate string for Value of the % header field with this name. If the name and value are both nonempty and % this object is not a subclass, look for a subclass that implements the Name % and use it to validate or convert the value, and return that value. % Otherwise if no appropriate subclass is found use a generic converter in % this base class. If this is a subclass already, we trust that the subclass % has overridden valueToString or scalarToString to convert the value to a % string, so we'll just call those functions. if isempty(value) && ~ischar(value) % If [] nothing to validate rval = []; elseif isempty(name) % no name; call converter. It will be the % generic converter in the base class, or a possibly custom % converter in the subclass. rval = obj.callValueToString(value); elseif ~obj.isBaseClass() % Name specified and we're a subclass % Make sure name is legal for this subclass names = obj.getSupportedNames(); if ~isempty(names) && ~any(strcmpi(name, names)) error(message('MATLAB:http:IllegalNameForField', char(name), ... strjoin(cellstr(names)), class(obj))); end % Name OK; call converter. It will be the % generic converter in the base class, or a possibly custom % converter in the subclass. rval = obj.callValueToString(value); else % Name specified and we're the base class: see if there's a subclass % for this name. If so, fetch its value (which does necessary % validation and conversion). field = createField(name, value); if isempty(field) % no subclass, so use generic converter rval = obj.callValueToString(value); else rval = field.Value; end end end function errorCheck(obj, str, value) % Throw an appropriate error if str is []. Intended to be called after % obj.scalarToString(value) to determine whether conversion succeeded. if ~ischar(str) && isempty(str) obj.throwValueError('MATLAB:http:BadFieldValue', value); end end function tf = isBaseClass(obj) % True if this is the base class tf = metaclass(obj) ==?matlab.net.http.HeaderField; end function rval = callValueToString(obj, value) % Call valueToString(value) with zero, one or two additional arguments. % Behavior of this is subclass-specific, as it uses allowsStruct() and % allowsArray(), so it may change when a subclass is added in the future, but % such a change should only be to allow new types of values to be specified or % to more strictly enforce constraints on the syntax of the value. if obj.allowsStruct() rval = obj.valueToString(value, ', ', '; '); elseif obj.allowsArray() rval = obj.valueToString(value, ', '); else rval = obj.valueToString(value); end % Disallow non-visible characters other than space and tab, and all must % be ASCII. This is defined as field-content in % http://tools.ietf.org/html/rfc7230#section-3.2, which is sequence of % VCHAR (%21-7E), SP (%20), HTAB (%11) or obs-text (%80-FF). badChar = regexp(rval, '[^\x11\x20-\x7e\x80-\xff]', 'match', 'once'); if ~isempty(badChar) && ~ismissing(badChar) && badChar ~= '' % print illegal char in hex, as it's not likely visible error(message('MATLAB:http:IllegalCharInValue', ... dec2hex(char(badChar),2))); end end function rval = quoteTokens(obj, value, index, fieldName, delims) % Quote any tokens in value, if needed. See quoteValue() for detailed % description of quoting. % value - the string, converted from user input % index (optional) - the index of this value in array of input values, % always at least 1 if present. % fieldName (optional) - the name of the field, if this value came from a % struct; else []. % delims (optional) - delimiters % This function calls out to getTokenExtents() to determine where the tokens % lie in the value. if isempty(value) rval = value; return; end if nargin < 4 fieldName = []; if nargin < 3 index = 1; end end extents = obj.getTokenExtents(value, index, fieldName); if ~isempty(extents) % preallocate, assuming possible quotes but no escapes value = char(value); % TBD string rval(length(value)+2) = ' '; rx = 1; eEnd = 0; for i = 1 : size(extents, 1) % first insert any non-token chars before token eStart = extents(i,1); if eStart > eEnd + 1 pad = value(eEnd+1:eStart-1); rval(rx:rx+length(pad)-1) = pad; rx = rx + length(pad); end eEnd = extents(i,2); % inserted quoted token if nargin > 4 val = obj.quoteValue(value(eStart:eEnd), delims); else val = obj.quoteValue(value(eStart:eEnd)); end val = char(val); % TBD string rval(rx:rx+length(val)-1) = val; rx = rx + length(val); end % add remaining chars after token rval(rx:rx+length(value)-eEnd-1) = value(eEnd+1:end); rval = rval(1:rx-1); else rval = value; end rval = string(rval); % TBD string end function tf = isStringMatrix(obj, value) % Return true if useStringMatrix() and value is an Nx2 string matrix tf = obj.useStringMatrix() && isstring(value) && ismatrix(value) && ... size(value,2) == 2; end function str = defaultConvert(obj, value) % defaultConvert Convert the value to a string using default conversion rules. % This converts a single scalar value taken from an array or struct member % provided by the user. Caller has verified that value is a scalar or char % vector. Does not determine whether value needs to be quoted. % % If numeric, error out if not real % Convert using string (which converts numbers like num2str) % If that errors, convert using char % If that errors, throw error % If result is not a scalar string, throw error if isnumeric(value) validateattributes(value, {'numeric'}, {'real'}, class(obj), 'Value'); str = string(value); else e = []; try str = string(value); % handles string, char, numbers catch try str = string(char(value)); catch e end end % Handle cases where value might override char or string to return something % unexpected, like a matrix or non-string. Use disp to display value in % message. if ~isempty(e) || ~isscalar(str) || ~isstring(str) error(message('MATLAB:http:CannotConvertToString', ... strtrim(evalc('disp(value)')))); end end end end methods (Sealed) function value = parse(obj,varargin) % parse Parse the Value of the header field and return strings % This function parses the Value according to a generic set of rules, where % all the values are returned as string objects. Use this method to % process header fields for which there is no custom convert method. % % VALUE = parse(obj) % The Value of the header field is parsed according to a generic set of % rules, where all the values are returned as string objects. The Value % is first parsed as a list of comma-separated strings, each of which % become elements of the result vector. Each element will be treated % either a simple string, struct of semicolon-separated values, or struct % of NAME=VALUE pairs. The NAME of each struct field will be converted to % a valid MATLAB identifier using matlab.lang.makeValidName and % matlab.lang.makeUniqueStrings to resolve duplicates. If a struct field % is a simple token (i.e., just a VALUE and not a NAME=VALUE), the name % 'Arg_N' will be used for the field, where N is the ordinal position of % the field in the struct. % % This function uses parsing rules based in part on sections 3.2.4-3.2.6 % of <a href="http://tools.ietf.org/html/rfc7230">RFC 7230</a>, and augmented to interpret multiple values, and it assumes % that the field may contain multiple values (vectors) or name-value % pairs (structs). Therefore, for example, if the value contains a quoted % string, it is processed as a single token, where delimiters in the % string become part of the string and the result is the string with % quotes and backslashes of quoted pairs removed. Comments (text % surrounded by open-closed parentheses) are retained (including the % parentheses), but are treated as single tokens with possible escapes, % similar to quoted strings. % % If the field contains one or more comma-separated elements, none of % which look like structs (i.e., have no semicolons or = signs) the VALUE % returned is a vector of strings. If any of the values look like % structs, VALUE is an array of structs. % % If the input is a vector of HeaderFields, this method concatentates the % results of parsing each of the fields into a single array. This could % result in a cell array if the values are not of the same type. % % VALUE = parse(obj, FIELDS) % This lets you specify the names of struct fields to be created that are % not named. FIELDS is a string vector, char vector or cell array of char % vectors. If the Nth field of a struct has no name, and corresponding % Nth name in FIELDS exists and is nonempty, it will be used instead of % Arg_N. Using this syntax forces the returned value to be a struct (or % vector of them) with at least as many fields as the length of FIELDS. % Typically this pattern occurs in header fields that begin with a token % followed by attribute pairs. For example the Content-Type field has a % value with the syntax: % % media-type name1=value1; name2=value2; ... % % where "media-type" consists of a token whose field name would normally % be Arg_1. If you want that field to be called "MediaType", you can % specify: % headerField.parse('MediaType'); % % VALUE = parse(___, PARAM1, VALUE1, ... , PARAMn, VALUEn) % Specifies the delimiters to use for arrays and structs, instead of % comma and semicolon. Valid parameter names are: % 'ArrayDelimiters' specifies delimiters separating array elements % 'MemberDelimiters' specifies delimiters separating struct fields % The parameter name is case-insensitive, but the full word must be % specified. The VALUE of a PARAM is a string vector, char vector or % cell vector of regular expressions specifying the possible delimiters, % interpreted in the order they appear in the vector. Specify '' if you % do not want this field to be parsed as an array or struct, but you % still want quoted string and escape processing. Specify [] if you do % not want the field to be parsed as an array or struct and also do not % want quoted string or escape processing within that array element or % struct value. % % As an example, with no optional parameters, the following field value: % % text/plain;q=0.5;format=flowed, text/*;level=1, image/jpeg % % will be interpreted as an returned as an array of 3 structs: % % Arg_1: 'text/plain' 'text/*' 'image/jpeg' % q: '0.5' [] [] % format: 'flowed' [] [] % level: [] '1' [] % % The empty fields were added because a struct array must have the same % fields in all elements. % % As another example, consider the header field: % % Server: CERN/3.0 libmww/2.17 foo % % The following: % % VALUE = parse(obj, {'product','comment'}, 'ArrayDelimiters', '\s', % 'MemberDelimiters', '/') % returns an array of 3 structs: % % product: 'CERN' 'libmww' 'foo' % comment: '3.0' '2.17' [] % % To obtain the value as a cell array of strings without interpreting the % strings as possible structs: % % VALUE = parse(obj, 'ArrayDelimiters', '\s', 'MemberDelimiters', []) % % returns: % % ["CERN/3.0", "libmww/2.17", "foo"] % % See also Name, Value, matlab.lang.makeValidName, convert % matlab.lang.makeUniqueStrings % Internal behavior, for subclass designers: % An additional PARAM,VALUE pair is supported: % '_custom' true if we should invoke subclass-specific behavior to parse % this field, by invoking functions such as allowsArray() or % allowsStruct() that subclasses may override. Subclasses % should set this property to invoke that behavior. Default % is false. import matlab.net.internal.* skip = false; custom = false; % This argument parser actually allows the FIELDS parameter to be in any % position persistent publicParams params paramsStr if isempty(params) publicParams = {'arraydelimiters','memberdelimiters'}; params = [publicParams '_custom']; paramsStr = string(params); end for i = 1 : length(varargin) if skip skip = false; continue; end arg = varargin{i}; if iscell(arg) || (isstring(arg) && ~isscalar(arg)) % If array of strings, it's structFields if exist('structFields','var') error(message('MATLAB:http:DuplicateStructFields')); end structFields = getStringVector(arg, mfilename, 'FIELDS', true); else % Not array of strings; must be single string. Need to disambiguate between a % named parameter or a 1-element FIELDS array that happens to have the name of % a named parameter. strarg = getString(arg, class(obj), 'FIELDS'); % If we already have FIELDS, assume named parameter if it's not the last % argument. lastarg = length(varargin) == i; if ~lastarg nextarg = varargin{i+1}; end isnamed = exist('structFields','var') && ~lastarg; % ~isnamed at this point says it might be FIELDS. if ~isnamed && ~lastarg % Might be FIELDS and there is another argument. Assume it's FIELDS if the % next argument is a named parameter. otherwise assume (for now) this is a % named parameter. Arbitrarily assume that the next argument is a named % parameter only if has at least 5 chars. If wrong, we'll still use that as a % named parameter if the current argument isn't one. nextarg = varargin{i+1}; isnamed = ~((isstring(nextarg) || ischar(nextarg)) && ... strlength(string(nextarg)) > 5 && ... any(paramsStr.startsWith(lower(nextarg)))); end if isnamed % come here if named parameter is the only valid option or if % next argument isn't a named parameter assert(~lastarg); % impossible due to above e = []; try % check for named parameter but don't error out if it's not in this list strarg = validatestring(strarg, params); catch e end if isempty(e) % named parameter found switch strarg case 'arraydelimiters' if ~isempty(nextarg) nextarg = getStringVector(nextarg, class(obj), arg); end arrayDelims = nextarg; skip = true; case 'memberdelimiters' if ~isempty(nextarg) nextarg = getStringVector(nextarg, class(obj), arg); end structDelims = nextarg; skip = true; case '_custom' validateattributes(nextarg, {'logical'}, {'scalar'}, 'HeaderField.parse', arg); custom = nextarg; skip = true; end else % If not a named parameter, it's an error if we already have a FIELDS % argument. If not, assume it's FIELDS and not a named parameter. if exist('structFields','var') % for purposes of the message, only list the publicParams validatestring(strarg, publicParams, mfilename); else structFields = strarg; end end else % If not a named parameter, assume FIELDS if exist('structFields','var') validatestring(strarg, publicParams, mfilename); error(message('MATLAB:http:MissingValueForParam', char(arg))); end structFields = strarg; end end end if ~exist('arrayDelims','var') arrayDelims = ','; end if exist('structFields','var') % After removing empty placeholders, check for illegal or duplicate names. sf = structFields(structFields ~= ''); invalid = find(arrayfun(@(s) ~isvarname(char(s)), sf), 1); if ~isempty(invalid) error(message('MATLAB:http:InvalidFieldName', char(sf(invalid)))); end if length(unique(sf)) ~= length(sf) error(message('MATLAB:http:NamesNotUnique', char(strjoin('"' + sf + '"')))); end end if exist('structDelims','var') % these always return struct if exist('structFields','var') value = parseField(obj, arrayDelims, structDelims, structFields, custom); else value = parseField(obj, arrayDelims, structDelims, custom); end else if exist('structFields','var') % this forces return of struct value = parseField(obj, arrayDelims, ';', structFields, custom); else % this possibly returns plain string(s) if no semicolons or equals value = parseField(obj, arrayDelims, custom); end end end function str = string(obj) % string Return header field as a string % STR = string(fields) returns the array of HeaderField objects as a string, % as it would appear in a message, with newlines inserted between the % fields but not at the end of all the fields. if ~isscalar(obj) if isempty(obj) str = ''; else strs = arrayfun(@string, obj, 'UniformOutput', false); str = strjoin([strs{:}], sprintf('\n')); end else % a single obj gets no newline if isempty(obj.Value) v = ''; else v = obj.Value; end if isempty(obj.Name) n = string(''); else n = obj.Name; end str = n + ': ' + v; end end function str = char(obj) % char returns the header field array as a character vector. % For more information see string. % % See also string. str = char(string(obj)); end end methods (Sealed, Access=protected, Hidden) function value = parseField(obj, varargin) % Parse and return the value of this header field % VALUE = parseField(HEADERFIELD, arrayDelims, structDelims, structFields, custom) % This function is called by parse() to parse the Value property in the % header as an array of strings, where each string may be interpreted as a % struct, as described in parse. HEADERFIELD is a scalar or vector of % HeaderFields. % % arrayDelims - string or cell array of regular expressions specifying % delimiters between array elements. If missing, ',' is % used. If '' or allowsArray is overridden to return % false, the value is processed as a single array element. % Quoted string and escape processing is applied to array % elements. If an empty array ([]), the Value is not % processed as an array (i.e., no quoted string or escape % processing is done, and this function returns either a % scalar struct or a string). Optional whitespace on % either side of an arrayDelim is allowed and ignored. % structDelims - optional; passed to parseElement for each array element. % structFields - optional; passed to parseElement for each array element. % custom - optional: true to use subclass-specific behavior to parse % the field; false otherwise. Default is true, which is % what most subclasses will want if they call this method. % If false the default values of all overridden functions % such as allowsArray() will be used. If present this % parameter must be last. % % This function parses the Value in this header field, breaking it up into % array elements (strings) as specified by arrayDelims, and passes each % element to parseElement (along with the structDelims and structFields % parameters) for further processing. The return values from parseElement % are assembled into either a cell array of strings or array of objects, % depending on the types of values returned, or 3-D array of strings if % useStringMatrix() is set. % % This function treats strings surrounded by quotes or open-closed % parentheses as tokens, so will ignore arrayDelims or structDelims within % them. % % If parseElement returns only strings, this function returns a cellstr % (same as if structDelims was specified as []). % % If custom is false or useStringMatrix() is not set, and parseElement % returns structs or structs and strings, this function returns an vector % of structs with fields that are the union of all struct fields returned % by parseElement, where unset fields have empty values and strings become % structs with a single field whose name is 'Arg_1' or (if specified) % structFields{1}. The combination of structs and strings results only if % structDelims is missing. % % If custom is true and useStringMatrix is set, and parseElement returns % Nx2 string matrices: % Pad each Nx2 matrix with rows so their first dimensions are all the % same. Padding uses empty strings. % Return an MxNx2 array, where M is number of elements. % % If the input is a vector, this method behaves as if all the values were % in one field separated by an ArrayDelims. In other words, it % concatenates the result of parsing each of the HeaderFields into single % vector or cell vector if the results are heterogeneous. % % Subclasses that override convert may use this as a utility to parse the % header field with specific array or struct delimiters. % % VALUE = parseField(HEADERFIELD, arrayDelims, PARSER) % Splits the Value of the header into elements at the delimiters specified % in arrayDelims and pass each element to PARSER for processing. PARSER % is a handle to a function that takes a string and returns either a % struct, string or scalar value or object. This usage is for the benefit % of subclasses that override convert, which want generic escape and array % processing for the field but with a custom parser for the element % values. This usage always invokes subclass-specific behavior. % % This function assembles the values received from PARSER into an array or % cell array, depending on the types of values received: % % If all values are strings or structs, returned value is as described % for the non-PARSER usage above. % % If all values are of exactly the same type or have a common % matlab.mixin.heterogeneous type, the returned value is an array of % those types. % % If values are of different non-heterogeneous types, the returned % value is a cell array of those values. if isempty(obj) value = obj.callParseField([]); else for i = 1 : length(obj) fValue = obj(i).callParseField(obj(i).Value, varargin{:}); if i == 1 value = fValue; else % append the individual results to the end of the value array if iscell(value) value = [value num2cell(fValue)]; %#ok<AGROW> else try value = [value fValue]; %#ok<AGROW> catch % above can fail if previous value and fValue are not % matlab.mixin.Heterogeneous. In that case, convert % value into cell array. c1 = num2cell(value); c2 = num2cell(fValue); value = [c1(:)' c2(:)']; end end end end end end function value = callParseField(obj, str, varargin) % VALUE = callParseField(HEADERFIELD, STR, arrayDelims, structDelims, structFields, custom) % All arguments after STR are optional; placeholders required % Same as parseField but parses str instead of this field's value. % Placeholder not required for custom argument. % % obj must be a scalar. if isempty(obj) || isempty(obj.Value) value = []; else parser = @(obj, varargin) parseElement(obj, varargin{:}); % If last argument is a logical, it's the custom parameter. if ~isempty(varargin) && islogical(varargin{end}) && ~varargin{end} % custom = false, so pass in defaults for allowsArray and useStringMatrix value = matlab.net.http.internal.fieldParser(... str, obj, parser, true, false, varargin{:}); else value = matlab.net.http.internal.fieldParser(... str, obj, parser, obj.allowsArray(), obj.useStringMatrix(), varargin{:}); end end end function value = parseElement(obj, str, varargin) % Return the value of one array element of a header field % parseElement(obj, str, structDelims, structFields, custom) % This function is called by parseField to process each array element (a % string) extracted from the Value of the header. % % str - the string to be processed % structDelims - optional string or cell array of regular expressions % specifying delimiters between struct elements. If % missing, semicolon is used. If an empty array, or if % missing and the string contains no semicolons, or % allowsStruct is overridden to return false, this function % returns str; otherwise it always returns a struct with at % least one field even if the string contains no % structDelims. Optional whitespace is assumed on either % side of a delimiter, which will be ignored. % structFields - optional names to use for unnamed fields (those without % name=value syntax), as described in parse. Ignored % if structDelims is empty. The returned struct will % always contain at least these fields, even if the string % has fewer fields. Unset fields will have empty values. % custom - optional true or false to implement subclass-specific % parsing based on overloaded methods such as allowsArray() % and allowsStruct(). Default is true, which is what most % subclasses will want to do. Specifying this argument does % not require filling in placeholder for structDelims or % structFields. % % Subclasses that override convert may use this function as a utility to % parse the value as a struct, perhaps with custom delimiters or field % names. if isempty(obj) value = string.empty; else if ~isempty(varargin) && islogical(varargin{end}) % If last arg is a boolean, it's the custom flag. % Save it and remove the arg. custom = varargin{end}; varargin(end) = []; else custom = true; end if custom value = matlab.net.http.internal.elementParser(... str, obj.allowsStruct(), obj.useStringMatrix(), varargin{:}); else % in the non-custom case, use all the defaults value = matlab.net.http.internal.elementParser(... str, true, false, varargin{:}); end end end function exc = getValueError(obj, id, value, varargin) % exc = getValueError(id, value) returns an MException with the specified id % Creates a message using the specified id providing these arguments: % {0} Name of the field (or class if Name is empty) % {1} Class of value % {2} Stringified value % {3...} any additional arguments from varargin if isstring(value) || iscellstr(value) v = value; else try v = num2str(value); catch try v = char(value); catch % if it has no char or num2str method, disp it, using cellstr to % remove trailing empty lines and bracketing with newlines c = cellstr(evalc('disp(value)')); v = sprintf('\n%s\n', c{1}); end end end % v now a string array, char array or cellstr name = obj.Name; if isempty(name) name = class(obj); end % if v has more than one row, concatentate with space separators % otherwise leave alone because cellstr strips trailing spaces if (iscellstr(v) && isrow(v)) || isstring(v) v = strjoin(v, ' '); end if ~isrow(v) v = strjoin(cellstr(v),' '); end exc = MException(message(id, char(name), class(value), char(v), varargin{:})); end function throwValueError(obj, id, value, varargin) % throwValueError(id, value) throws an MException with the specified id % Creates a message using the specified id providing these arguments: % {0} Name of the field (or class if Name is empty) % {1} Class of value % {2} Stringified value % {3...} any additional arguments from varargin throw(getValueError(obj, id, value, varargin{:})); end end methods (Static, Access=protected, Hidden) function converted = convertObject(~, value) % Converter whose sole purpose is to convert [] to an empty HeaderField array if isempty(value) converted = matlab.net.http.HeaderField.empty; else converted = []; % generates MATLAB error end end end methods (Access=protected, Hidden) % These methods would be overidden only by classes that don't want the % default field constructing behavior, where the methods such as allowsArray, % allowsStruct and getStringException aren't sufficient to specify behavior. % This could be because the array or struct delimiters are something other % than comma and semicolon, or the default conversions from MATLAB types to % string aren't appropriate (or don't work). function str = valueToString(obj, value, arrayDelim, structDelim) % Convert the value to a string to be stored in the header. % This function is used to construct a header field value, and it accepts % values that are either strings or objects that can be converted to % strings. % % It called by set.Value to convert the provided value to a string. This % base class method accepts a char matrix, cell array of strings, struct % array, or array of any type whose elements can be converted by % defaultConvert(). It produces a list of tokens (strings) for each % element of the value array or row of a char matrix, using arrayDelim as % separator between tokens and structDelim for separator between struct % fields. If an element of the value is a string and getStringException % returns [] to indicate the string is valid, the element becomes the token % unchanged; otherwise the token is obtained by calling scalarToString to % convert the element, passing in structDelim as a separator in case the % value is a struct. In summary: % % Input: string % Returns: the same string (unchanged) % % Input: v1 or [v1 v2 v3] or {v1 v2 v3} % Returns: [scalarToString(v1) arrayDelim scalarToString(v2) arrayDelim ...] % % Both arrayDelim and structDelim are optional; the default values are % comma and semicolon, respectively. % % In this base class, if the value is a single string (not cell array), it % (or the result of scalarToString) is returned unchanged. In all other % cases of string or scalar elements (but not structs) each token is % further processed by quoteValue to add double-quotes and escapes in % strings that have characters not allowed in tokens. Thus, if you have a % value and don't want this quote processing, convert the value to a single % string before storing it in the Value. If you do want quote processing, % and have just a single string, place it in a cell array of length 1. If % the input is a struct array, quote processing is not done. % % Subclasses may override this if they want to convert array values % differently or to specify array delimiters other than the default, but % they must obey the contract of this method that if the input is a single % string that is valid, the result is the same string. If the need is to % use default array processing but only to control processing of individual % array elements, subclasses should override scalarToString and/or % getStringException instead. If the need is only to disallow values that % are arrays or structs, but not implement special processing, override % allowsArray or allowsStruct. If you do override this, it is up to you to % check that the value is not an array or struct, if not allowed. % % Subclasses can expect that this function is called exactly once each time % the value is set. Implementations that override this function must not % assume that the header's Name field is set -- processing should depend % only on arguments passed into this function. % % Subclasses overriding this method will not be passed a structDelim % parameter if allowsStruct returns false, or an arrayDelim parameter if % allowsArray and allowsStruct return false. But if allowsStruct is true, % both parameters (possibly empty) will be present, so in that case % subclasses must declare them. % % If your class does not allow array values (allowsArray is false), you % should call throwValueError('MATLAB:http:ArraysNotAllowed') if the input % parameter is not a scalar, unless your intent is to create a % single-valued string out of the array. % % Typical pattern: % % function str = scalarToString(obj, value, varargin) % if isa(value, 'matlab.net.URI') % str = string(value); % elseif isstring(value) % str = scalarToString@matlab.net.http.HeaderField(obj, value, varargin{:}); % else % % not a string or URI: this produces guaranteed error % validateattributes(value, {'matlab.net.URI', 'string'}, {}, ... % class(obj), 'URI'); % end % end if nargin < 4 && obj.allowsStruct() structDelim = string('; '); elseif nargin >= 4 && ischar(structDelim) structDelim = string(structDelim); end if nargin < 3 if obj.allowsArray() arrayDelim = string(', '); elseif obj.allowsStruct() arrayDelim = []; end elseif ischar(arrayDelim) arrayDelim = string(arrayDelim); end if isscalar(value) && ~ischar(value) && ~isstring(value) % a scalar represents one element if isstruct(value) % If a struct, convert it to string. Any quoting must be done % within scalarToString if obj.allowsStruct() str = obj.scalarToString(value, [], 1, [], structDelim); elseif obj.allowsArray() str = obj.scalarToString(value, [], 1); else str = obj.scalarToString(value, []); end else % Non-structs get their value quoted only if the value allows % structs and the value contains an arrayDelim or structDelim, % or it allows array and the value contains arrayDelim. if obj.allowsStruct() % if struct alloweds, quote both array and struct delims str = obj.quoteTokens(obj.scalarToString(value, [], 1, [], ... [arrayDelim structDelim])); elseif obj.allowsArray() % of arrays allowed, quote array delims str = obj.quoteValue(obj.scalarToString(value, [], 1, ... arrayDelim)); else % If no arrays or structs allowed, don't quote anything str = obj.scalarToString(value, []); end end obj.errorCheck(str, value); elseif (ischar(value) && isrow(value)) || ... isstring(value) && isscalar(value) % A scalar string is treated as a raw header field value that might % contain an arrayDelim-separated list of values beyond what % scalarToString can process. Our job here is just to validate that % the string is OK, not to change it. value = string(value); e = obj.getStringException(value); if ~isempty(e) % String wasn't acceptable; maybe input was an array of % acceptable strings if obj.allowsArray() % If arrays allowed, split into elements at arrayDelim and % process each through scalarToString, and then % reconcatenate. This gives scalarToString a chance to % validate and possibly convert the strings. When splitting % at arrayDelim, replace spaces with zero or more whitespace % chars. delim = regexprep(arrayDelim, ' +', '\\s*'); splitValue = matlab.net.http.internal.delimSplit(... value, delim); % validate each element empties = arrayfun(@(v) ... isempty(obj.scalarToString(v, e, 1)), splitValue); if any(empties) err = []; else err = ''; end else % validate the whole string err = obj.scalarToString(value, e); end obj.errorCheck(err, value); end str = value; % string OK; return it elseif ~obj.allowsArray() obj.throwValueError('MATLAB:http:ArraysNotAllowed',value); % Everything past here is an array of more than one element elseif ischar(value) && ismatrix(value) % a char matrix with 2 or more rows; each row is a value numrows = size(value); str = string(''); idx = 1; charDelim = char(arrayDelim); for i = 1 : numrows if i ~= 1 str{1}(idx:idx+length(charDelim)-1) = charDelim; idx = idx+2; end row = value(i,:); e = obj.getStringException(row); if isempty(e) val = row; else val = obj.scalarToString(row, e, i); obj.errorCheck(val, row); end % quote processing on each row if obj.allowsArray() val = obj.quoteTokens(val, i, [], arrayDelim); else val = obj.quoteTokens(val, i, []); end len = strlength(val); str{1}(idx:idx+len-1) = char(val); idx = idx+len; end str{1}(idx:end) = []; elseif isvector(value) % it's a vector; accept either cell vector or regular vector values(length(value)) = string(''); for i = 1 : length(value) if iscell(value) valin = value{i}; else valin = value(i); end if ((ischar(valin) && isrow(valin)) || ... (isstring(valin) && isscalar(valin))) ... && isempty(obj.getStringException(valin)) % element is a string val = string(valin); else % element is not a string: convert it to string, using % structDelim if struct is allowed if obj.allowsStruct() val = obj.scalarToString(valin, [], i, arrayDelim, structDelim); else val = obj.scalarToString(valin, [], i); end obj.errorCheck(val, valin); end if ~isstruct(valin) % quote processing on non-structs if obj.allowsArray() values(i) = obj.quoteTokens(val, i, [], arrayDelim); else values(i) = obj.quoteTokens(val, i, []); end else values(i) = val; end end str = strjoin(values, arrayDelim); else obj.throwValueError('MATLAB:http:ValueNotVector',value); end if isempty(str) && ~isstring(str) obj.throwValueError('MATLAB:http:BadFieldValue',value); end end function str = scalarToString(obj, value, exc, index, arrayDelim, structDelim) % Convert the scalar value or string to a header field value % This function is called by valueToString to convert a single element of % an array used to set the Value in the field. It is invoked only for % scalars or strings. For strings, it is invoked only if % getStringException() returns nonempty (indicating the string as provided % needs to be converted or is invalid). Implementations of this method can % assume that if value is a string, it is invalid or needs conversion. % % value - The scalar value to convert % exc - The MException returned from getStringException % Set only if value is a string or char vector. % May be false if the string is needs processing. % index (optional) - the position of the scalar in the array, if % arrays are allowed. Not present if % allowsArray() is false. % arrayDelim (optional) - Not present if allowsArray() is false. % structDelim (optional) - Not present if allowsStruct() is false. % % By default, throws exc if the value is a scalar string (since this % means getStringException has already rejected it). Overriding this method % to process a scalar string makes sense if you want to convert the input % string to a different string, rather than allowing the literal string to % be used. % % If the value is not a struct, converts value using defaultConvert(). % % If the value is a struct (or, if useStringMatrix is set, an Nx2 string % matrix), returns a structDelim-separated list of name=value pairs in the % order they appear in the structure (or matrix), except that struct fields % with names of the form 'Arg_N' (or the N,1 element of the string matrix % is an empty string) are inserted with no names at the position indicated % by the number N (or at the end if the name is Arg_End), and empty fields % are skipped. Nonempty fields of the struct must be strings or scalars. % Each value is processed by quoteValue to quote strings with array or % struct delimiters. % % If the value is not a struct, the caller (valueToString, in the default % case) is responsible for deciding whether to quote delimiters. % % For example, given ';' as structDelim, the struct: % % Arg_2: 'value2' % Foo: 'value1' % Any: [] % Arg_End: '(com)ment)' % Bar: 'valu;"e3' % % returns: % % Foo=value1;value2;Bar="valu;\"e3";(com\)ment) % % If the input value is invalid, this function either throws an error or % returns []. If this returns [], and the caller is valueToString, a % generic error will be produced that refers to the class and name of the % field. Note that an empty string, '', is a valid return value which does % not signal an error. % % Subclasses should override this to provide custom conversions for scalar % or struct values, or to control the value of structDelim. Since, if the % value is a string, this is called only after getStringException() has % returned false or an MException, subclasses that don't want to convert % the string should throw an appropriate error (explicitly or by calling % this superclass method to return the exception received from % getStringException()) or return [] on any string to return a generic % error. For other types, if conversion fails, subclasses should throw an % error or return []. Subclasses overriding this method will not be passed % a structDelim argument if allowsStruct is false. % % If you override this method, it is safest to declare it this way: % % function str = scalarToString(obj, value, varargin) % % and pick off the additional arguments from varargin, if they are present. % % The result returned by this function will be escaped and quoted, if % necessary. If you have already quoted and escaped your string, override % getTokenExtents() to return []. if nargin < 5 structDelim = []; if nargin < 4 index = 1; end end if ischar(value) || isstring(value) % it has to be a simple string % The simple string case is always an error if isempty(exc) % We get here if caller invoked us on a string without setting % exception. In that case throw a generic exception. throwValueError(obj, 'MATLAB:http:BadFieldValue', value); else throw(exc); end elseif isstruct(value) || obj.isStringMatrix(value) % TBD this needs cleanup for the isStringMatrix case if obj.allowsStruct() if obj.useStringMatrix() fields = cellstr(value(:,1)); else fields = fieldnames(value); end lf = length(fields); strs = strings(1,lf); argStrs = strings(1,lf); for i = 1 : lf % for each field of the struct, insert an element in strs % that's either the value as a string or name=value ns = fields{i}; if obj.useStringMatrix() v = value(i,2); else v = value.(ns); end % convert the value and add quotes if it needs it v = obj.defaultConvert(v); v = obj.quoteTokens(v, index, ns, [arrayDelim structDelim]); if nargin < 4 structDelim = string(';'); end % checkValueForDelim(v, {arrayDelim structDelim}); if obj.useStringMatrix() idx = 0; else idx = strfind(ns, 'Arg_'); end if isempty(ns) || ... (~isempty(idx) && idx == 1 && ... ~isempty(regexp(ns(5:end),'^[1-9]\d*$', 'once'))) % If a field name begins with Arg_ and followed by a % number N, or is empty, put just the value (not the % name) in the N'th position argStrs. Don't have to % worry about N being used twice because field names % can't repeat. if idx ~= 0 argno = str2double(ns(5:end)); else argno = i; end argStrs(argno) = v; else strs(i) = string(ns) + '=' + v; end end % for each nonempty string in argstrs, insert it into its proper % place in strs for i = 1 : length(argStrs) arg = argStrs(i); if ~isempty(arg) strs = [strs(1:i-1) arg strs(i:end)]; end end strs(strs=='') = []; % remove empty elements str = strjoin(strs, structDelim); else obj.throwValueError('MATLAB:http:NoStructs', value); end else str = obj.defaultConvert(value); end end function exc = getStringException(~, ~) % Determine validity of input string for the field % exc = getStringException(obj, str) returns empty if the string is valid % for use in the field as is. Returns an MException (not thrown) if the % string is invalid. Returns false if the string needs to be further % processed by scalarToString to convert to a field value or throw an % error. Note the MException is returned, not thrown. % % This function, intended to be overridden by subclasses, is invoked by % valueToString on each string, string in a cell array, or row of a char % matrix to set the Value field. If this returns [], valueToString uses % the string as is. If this returns false or an MException, valueToString % will invoke scalarToString to try to convert the value to an acceptable % string. The default scalarToString will throw the MException if it is % passed a string. If you override this, throw an MException only if you % don't intend to override scalarToString to further process the string. % % If you want to throw a custom error for an invalid string, return an % MException. If you override scalarToString, check if a string is passed % in. If so, either throw a custom error or return [] to throw a generic % error. % % Default behavior of this function returns [], which says any string is % acceptable. Subclasses that want to validate strings should implement % a pattern something like this: % % try % convert str to expected object type that would be returned by % convert() or parse it, for e.g. % v = matlab.net.http.internal.elementParser(str, true, true); % if v is valid exc = [] % if invalid exc = exception or false % catch exc % end exc = []; end end methods (Static, Access=protected, Hidden) % These methods should be overridden by most subclasses to customize value % conversions. function tf = allowsArray() % Return true if this header field allows or contains lists or arrays % This function, intended to be possibly overridden by subclasses, is % invoked by methods in this class that store the Value field to determine % whether array processing should be done. The default is true. % % If false, attempt to set the Value property of this field to a non-scalar % results in an error, and the parse function will not attempt to % parse the Value as a list. tf = true; end function tf = allowsStruct() % Return true if this header field allows or contains structs % This function, intended to be possibly overridden by subclasses, is % invoked by methods in this class that store or return the Value field to % determine whether struct processing should be done. The default is true. % % If false, attempt to set the Value property of this field to a struct % results in an error, and the parse function will not attempt to % parse the value as a struct. tf = true; end function tf = useStringMatrix() % If true and allowsStruct is set, the name=value parameters in an element of % this field should be returned in an Nx2 string matrix instead of a % struct, and likewise an Nx2 string matrix is accepted to set the value % instead of a struct. By default this is false. It is overridden by % subclasses that return and accept field values in specialized objects. tf = false; end function tf = allowsTrailingComment() % Return true if this header field allows a comment at the end of the header % Default is true. If set and the header contains a comment, it may be % obtained from the Comment property. % % This setting applies only to comments at the ends of fields. Subclasses % that want to allow comments in particular positions inside elements of % the header need to override TBD tf = true; end function value = getDefaultValue % Return the default value for this field. This is the value that will be % stored in the field if it is set to [] or not specified in the % constructor. Default returns []. Subclasses need override this only if % they want to specify a different default. The value returned must be a % legal value acceptable to the set.Value function. value = []; end function tokens = getTokenExtents(value, index, field) %#ok<INUSD> % Given a string that represents the converted-to-string value of a % particular element of an array or struct that was passed in as a header % field value, return the an n-by-2 array of starting and ending token % positions. These are the tokens that need to be processed for possible % escapes and quotes. If this returns empty, no escape/quote processing is % done on the value. This is appropriate if the tokens have already been % checked to be valid tokens. % % This function is needed because field values provided by the user are not % necessarily simple tokens, but may contain delimiters. For example the % MediaType field of the AcceptField has the syntax "type/subtype" which is % actually 2 tokens separated by a slash. % field - If not empty, the name of the struct field being processed. % index - The index of the element; always at least 1 % Default returns [1 length(value)] indicating the entire value is a token. % % Subclasses who have already escaped and quoted their tokens in % scalarToString() should override this to return [] to indicate no quote % processing should be performed. tokens = [1 strlength(string(value))]; end function str = quoteValue(token, delims) % Quote the token (string) if it contains a sequences that need quoting. % delims - optional vector of strings to check for in addition to standard % This function is called by valueToString and scalarToString, before storing % a token, to quote (with double quotes) a token that contains unquoted % special characters or whitespace. Within quotes, backslash and % double-quote characters are escaped with backslash. The special characters % are those not allowed in tokens according to RFC 7230: % (),/:;<=>?@[\]{}" % plus all whitespace characters plus anything that matches one of the % delims. If the string is already quoted (i.e., begins and ends with paired % double-quotes or open-closed parentheses), the token is assumed to be % already formatted as a quoted string or comment and is returned unchanged. % % This method is only needed % % Subclasses that have different quoting rules may wish to override this. % Subclasses whose scalarToString() method has already quoted the returned % value, or which don't want any additional quoting of the value, should % override getTokenExtents() to return []. if isempty(token) str = token; return; end token = string(token); % See if starts with a quote character: " or ( if strlength(token) > 1 && (token.startsWith('"') || token.startsWith('(')) % It starts with a " or ( % See if it ends with the paired " or ) if token.startsWith('(') match = ')'; else match = '"'; end if token(end) == match % ends with a " or ), but we need to see if it's properly paired i = 2; while i < strlength(token)-1 ch = extractBetween(token,i,i); if ch == '\' i = i + 2; elseif ch == match % unescaped " or ) means it's not a quoted string or % comment after all break; else i = i + 1; end end % If the first unescaped matching quote is the last character of % the token, then it's a quoted string and we just return as is. % If we fall off the end of string (e.g., the last " is escaped) % or we didn't get to the end, it's not a quoted string or % comment. if i == strlength(token) % return as is str = token; return; end end end % It's not already a quoted string, so quote if necessary if nargin > 1 str = matlab.net.http.internal.quoteToken(token, delims); else str = matlab.net.http.internal.quoteToken(token); end end end methods (Static, Sealed, Hidden) function str = createQuotedString(value) % Given a string value, place double-quotes around it and add escapes % Creates quoted-pair (using backslash) for double-quote and backslash. No % reason to escape other chars. str = ['"' regexprep(value, '(\|")', '\$1') '"']; end function tf = isValidToken(value, allowQuotes, allowComments) % Return true if string is a valid HTTP token. This is a utility useful for % subclasses to validate strings to be inserted in HTTP field values. % Returns true if the string contains at least one character and only the % characters allowed in tokens as per section 3.2.6 of RFC 7230. White space % is not permitted anywhere in the value (except within comments and quotes) % % The optional allowQuotes and allowComments parameters, if true, specify % whether quoted strings and/or comments are allowed. In this case the token % is considered valid if it is completely surrounded by paired double-quotes % or parentheses and internal quotes or parentheses are aqpropriately % escaped, with no constraints on the characters within the string. if ~ischar(value) || ~isrow(value) || isempty(value) tf = false; return; end if (nargin > 1 && allowQuotes && value(1) == '"') || ... (nargin > 2 && allowComments && value(1) == '(') % allowQuotes and it begins with a quote, or allowComments and it % begins with a left paren if length(value) > 1 if allowQuotes closer = '"'; else closer = ')'; end % the string must end with the closer and there must be no % non-escaped closers in the string % remove escaped pairs unescapedChars = regexprep(value(2:end), '\\.', ''); % the only closer left should be at the end tf = strfind(unescapedChars, closer) == length(value) - 1; tf = isscalar(tf) && tf; else tf = false; end else % Caller doesn't allow comments or quotes, or allowed but isn't a % comment or quoted string, so check for any illegal characters. % It's worth noting that org.apache.http is much more liberal with % regard to what characters can occur in a token, compared to RFC % 7230. It's not clear what rules POCO uses. tf = isempty(regexp(value, '[^' + TokenChars + ']', 'once')); end end function str = qualityToString(value) % Parse value and return a string representing the quality value, suitable % for use in a q=weight parameter of a header field. If the value is empty, % return []. If the value is a string and it is valid, just return it. % Otherwise if the value is a number convert it to a quality string. A valid % quality must be between 0 and 1 inclusive. This method does not enforce % the syntax of the string, as long as it has a valid numeric value. isvalid = @(x)isreal(x) && isscalar(x) && x >= 0 && x <= 1; if isempty(value) str = []; elseif isnumeric(value) if isvalid(value) % stringify numbers < 1 by printing '0.' and up to 3 digits with % no trailing zeros str = sprintf('%1.3g',round(value,3)); else error(message('MATLAB:http:BadQuality', num2str(value))); end else num = matlab.net.internal.getString(value, mfilename, 'quality'); num = str2double(num); if isvalid(num) str = value; else error(message('MATLAB:http:BadQuality', value)); end end end end methods (Static, Sealed) function [fields, names] = displaySubclasses() % displaySubclasses Display supported HeaderField subclasses % HeaderField.displaySubclasses displays all subclasses of HeaderField in % the matlab.net.http.field package that you can construct, along with the % names of header fields they support. % % [FIELDS, NAMES] = HeaderField.displaySubclasses returns FIELDS, an array % of strings naming the subclasses, and NAMES, a cell array containing % vectors of strings containing the header field names that the subclasses % support: NAMES{i} contains the names supported by FIELDS(i). NAMES{i} is % empty if FIELDS(i) has no constraints on supported names. % Basically we just list all non-abstract classes in matlab.net.http.field % with public constructors. list = meta.package.fromName('matlab.net.http.field').ClassList; classes = arrayfun(@dispClass, list, 'UniformOutput', false); classes(cellfun(@isempty, classes)) = []; % remove empty cells classes = sort(classes); % this returns string vector or string.empty if nargout ~= 1 getNames = @(c) string(feval(['matlab.net.http.field.' char(c) '.getSupportedNames'])); end if nargout > 0 fields = string(classes); if nargout > 1 names = arrayfun(getNames, fields, 'UniformOutput', false); end else fprintf('%s\n',getString(message('MATLAB:http:DisplaySubclasses'))); if desktop('-inuse') cellfun(@(x) fprintf('%-*s %s\n', 72+length(x), ... sprintf('<a href="matlab:doc matlab.net.http.field.%s">%s</a>',x, x), ... strjoin(getNames(x), ', ')), classes); else % no links in nodesktop case cellfun(@(x) fprintf('%-24s %s\n', x, ... strjoin(getNames(x), ', ')), classes); end end function clsName = dispClass(clazz) % get name of the class after the last '.' clsName = clazz.Name(regexp(clazz.Name, '\.[^.]+$')+1 : end); assert(~isempty(clsName)); % find the constructor; the method with the same name methods = clazz.MethodList; constructor = methods(cellfun(@(x)strcmp(x,clsName), {methods.Name})); assert(isscalar(constructor)); if ~strcmp(constructor.Access,'public') || clazz.Abstract % can't create if constructor not public or class is abstract clsName = []; end end end end end function obj = instantiate(metaclass, varargin) % Instantiate the specifed class by calling its constructor with varargin parameters. % If "too many arguments" error occurs, and varargin has 2 arguments, try calling it % with just the 2nd argument (which is the value). try obj = feval(metaclass.Name, varargin{:}); catch e if strcmp(e.identifier, 'MATLAB:maxrhs') && length(varargin) == 2 obj = feval(metaclass.Name, varargin{2}); else rethrow(e); end end end function field = createField(name, varargin) % If a subclass of matlab.net.http.HeaderField in the matlab.net.http.field package % is found that implements the field name return the field object. Otherwise returns % []. metaclass = matlab.net.http.internal.PackageMonitor.getClassForName(name); if isempty(metaclass) field = []; else % Subclass found; instantiate it. This has the side-effect of validating the % name and value. if isscalar(string(feval([metaclass.Name '.getSupportedNames']))) % supports just one name, so constructor has no name argument field = instantiate(metaclass, varargin{:}); field.Name = name; % insure the name has the same case as what was given else field = instantiate(metaclass, name, varargin{:}); end end end