; ; NOSA HEADER START ; ; The contents of this file are subject to the terms of the NASA Open ; Source Agreement (NOSA), Version 1.3 only (the "Agreement"). You may ; not use this file except in compliance with the Agreement. ; ; You can obtain a copy of the agreement at ; docs/NASA_Open_Source_Agreement_1.3.txt ; or ; https://sscweb.gsfc.nasa.gov/WebServices/NASA_Open_Source_Agreement_1.3.txt. ; ; See the Agreement for the specific language governing permissions ; and limitations under the Agreement. ; ; When distributing Covered Code, include this NOSA HEADER in each ; file and include the Agreement file at ; docs/NASA_Open_Source_Agreement_1.3.txt. If applicable, add the ; following below this NOSA HEADER, with the fields enclosed by ; brackets "[]" replaced with your own identifying information: ; Portions Copyright [yyyy] [name of copyright owner} ; ; NOSA HEADER END ; ; Copyright (c) 2013-2025 United States Government as represented by the ; National Aeronautics and Space Administration. No copyright is claimed ; in the United States under Title 17, U.S.Code. All Other Rights ; Reserved. ; ;

;+ ; This class represents the remotely callable interface to ; <a href=“https://www.nasa.gov/“>NASA&lt;/a>‘s ; <a href=“https://spdf.gsfc.nasa.gov/“>Space Physics Data Facility</a> ; (SPDF) ; <a href=“https://en.wikipedia.org/wiki/Web_service#Representational_state_transfer”> ; RESTful Web services</a>. ; ; @copyright Copyright (c) 2013-2025 United States Government as ; represented by the National Aeronautics and Space Administration. ; No copyright is claimed in the United States under Title 17, ; U.S.Code. All Other Rights Reserved. ; ; @author B. Harris ;-

;+ ; Creates an object representing the SPDF Web service. ; ; If access to the Internet is through an HTTP proxy, the caller ; should ensure that the HTTP_PROXY environment variable is correctly ; set before this method is called. The HTTP_PROXY value should be ; of the form ; http://username:password@hostname:port/. ; ; NOTE: Due to support for the HTTP_PROXY environment variable, this ; class should not be used in a CGI-like environment where HTTP_PROXY ; can be set by untrusted entities (see httpoxy vulnerability). ; ; @param endpoint {in} {type=string} ; URL of SPDF web service. ; @param version {in} {type=string} ; class version. ; @param currentVersionUrl {in} {type=string} ; URL to the file identifying the most up to date version ; of this class. ; @keyword userAgent {in} {optional} {type=string} {default=SscWs} ; HTTP user-agent value used in communications with SPDF. ; @keyword sslVerifyPeer {in} {optional} {type=int} {default=1} ; Specifies whether the authenticity of the peer’s SSL ; certificate should be verified. When 0, the connection ; succeeds regardless of what the peer SSL certificate ; contains. ; @returns a reference to a SSC object. ;- function SpdfRest::init, endpoint, endpoint, version, currentVersionUrl, currentVersionUrl, userAgent = userAgent, $ sslVerifyPeer = sslVerifyPeer compile_opt idl2

self.endpoint = endpoint
self.version = version
self.currentVersionUrl = currentVersionUrl

self.ssl_verify_peer = self->getDefaultSslVerifyPeer()

if n_elements(sslVerifyPeer) gt 0 then begin

    self.ssl_verify_peer = sslVerifyPeer
endif

if ~keyword_set(userAgent) then userAgent = 'SscWs'

self.userAgent = 'User-Agent: ' + userAgent + ' (' + $
    !version.os + ' ' + !version.arch + ') IDL/' + !version.release

http_proxy = getenv('HTTP_PROXY')

if strlen(http_proxy) gt 0 then begin

    proxyComponents = parse_url(http_proxy)

    self.proxy_hostname = proxyComponents.host
    self.proxy_password = proxyComponents.password
    self.proxy_port = proxyComponents.port
    self.proxy_username = proxyComponents.username

    if strlen(proxy_username) gt 0 then begin

        self.proxy_authentication = 3
    endif
endif

self.retryLimit = 50

return, self

end

;+ ; Performs cleanup operations when this object is destroyed. ;- pro SpdfRest::cleanup compile_opt idl2

end

;+ ; Gets the default value for the IDLnetURL SSL_VERIFY_PEER property ; based upon the runtime version of IDL. ; ; @returns 0 if runtime version of IDL cannot verify new SSL certificates. ; Otherwise, 1. ;- function SpdfRest::getDefaultSslVerifyPeer compile_opt idl2 ; compile_opt static when support for IDL < 8.3 is not required

releaseComponents = strsplit(!version.release, '.', /extract)

if (releaseComponents[0] lt '7') or $
   (releaseComponents[0] eq '8' and $
    releaseComponents[1] lt '4') then begin

; Earlier versions of IDL cannot verify new SSL certificates.

    return, 0
endif

return, 1

end

;+ ; Gets the current endpoint value. ; ; @returns current endpoint string value. ;- function SpdfRest::getEndpoint compile_opt idl2

return, self.endpoint

end

;+ ; Gets the current userAgent value. ; ; @returns current userAgent string value. ;- function SpdfRest::getUserAgent compile_opt idl2

return, self.userAgent

end

;+ ; Gets the current defaultDataview value. ; ; @returns current defaultDataview string value. ;- function SpdfSsc::getDefaultDataview compile_opt idl2

return, self.defaultDataview

end

;+ ; Gets the version of this class. ; ; @returns version of this class. ;- function SpdfSsc::getVersion compile_opt idl2

return, self.version

end

;+ ; Gets the most up to date version of this class. ; ; @returns most up to date version of this class. ;- function SpdfSsc::getCurrentVersion compile_opt idl2

catch, errorStatus
if (errorStatus ne 0) then begin

    catch, /cancel

; Failed to get current version return, ” endif

url = obj_new('IDLnetURL', $
              proxy_authentication = self.proxy_authentication, $
              proxy_hostname = self.proxy_hostname, $
              proxy_port = self.proxy_port, $
              proxy_username = self.proxy_username, $
              proxy_password = self.proxy_password)

return, url->get(/string_array, url=self.currentVersionUrl)

end

;+ ; Compares getVersion() and getCurrentVersion() to determine if this ; class is up to date. ; ; @returns true if getVersion() >= getCurrentVersion(). Otherwise ; false. ;- function SpdfSsc::isUpToDate compile_opt idl2

version = strsplit(self->getVersion(), '.', /extract)
versionElements = n_elements(version)
currentVersion = strsplit(self->getCurrentVersion(), '.', /extract)
currentVersionElements = n_elements(currentVersion)

if currentVersionElements eq 0 then begin

; Do not know what current version is so return up-to-date return, 1 endif

if versionElements lt currentVersionElements then begin

    elements = versionElements
endif else begin

    elements = currentVersionElements
endelse

for i = 0, elements - 1 do begin

    if 0 + version[i] lt 0 + currentVersion[i] then return, 0
endfor

if versionElements lt currentVersionElements then begin

    return, 0
endif else begin

    return, 1
endelse

end

;+ ; Gets the node’s value of the first child of the first item of the ; specified element of the given DOM document. ; ; @private ; ; @param domElement {in} {required} {type=IDLffXMLDOMElement} ; DOM element to search. ; @param tagName {in} {required} {type=string} ; A scalar string containing the tag name of the desired ; element. ; @returns strarr containing the node’s string value(s) of the first ; child of the item(s) of the specified element of the given DOM ; document. An empty string is returned if the value cannot be ; found. ;- function SpdfRest::getNamedElementsFirstChildValue, $ domElement, tagName compile_opt idl2

nodeList = domElement->getElementsByTagName(tagName)

if nodeList->getLength() eq 0 then return, ''

values = strarr(nodeList->getLength())

for i = 0, nodeList->getLength() - 1 do begin

    domNode = nodeList->item(i)

    child = domNode->getFirstChild()

    if obj_valid(child) then begin

        values[i] = child->getNodeValue()
    endif else begin

        values[i] = ''
    endelse
endfor

if n_elements(values) eq 1 then return, values[0] $
                           else return, values

end

;+ ; Gets the node’s double value of the first child of the first item of ; the specified element of the given DOM element. ; ; @private ; ; @param domElement {in} {required} {type=IDLffXMLDOMElement} ; DOM element to search. ; @param tagName {in} {required} {type=string} ; A scalar string containing the tag name of the desired ; element. ; @returns dblarr containing the node’s double value(s) of the first ; child of the item(s) of the specified element of the given DOM ; document. A scalar constant of !values.d_NaN is returned if ; the value cannot be found. ;- function SpdfRest::getNamedElementsFirstChildDoubleValue, $ domElement, tagName compile_opt idl2

nodeList = domElement->getElementsByTagName(tagName)

if nodeList->getLength() eq 0 then return, !values.d_NaN

values = dblarr(nodeList->getLength())

for i = 0, nodeList->getLength() - 1 do begin

    domNode = nodeList->item(i)

    child = domNode->getFirstChild()

    if obj_valid(child) then begin

        values[i] = double(child->getNodeValue())
    endif else begin

        values[i] = !values.d_NaN
    endelse
endfor

if n_elements(values) eq 1 then return, values[0] $
                           else return, values

end

;+ ; Gets the node’s float value of the first child of the first item of ; the specified element of the given DOM element. ; ; @private ; ; @param domElement {in} {required} {type=IDLffXMLDOMElement} ; DOM element to search. ; @param tagName {in} {required} {type=string} ; A scalar string containing the tag name of the desired ; element. ; @returns fltarr containing the node’s float value(s) of the first ; child of the item(s) of the specified element of the given DOM ; document. A scalar constant of !values.f_NaN is returned if ; the value cannot be found. ;- function SpdfRest::getNamedElementsFirstChildFloatValue, $ domElement, tagName compile_opt idl2

nodeList = domElement->getElementsByTagName(tagName)

if nodeList->getLength() eq 0 then return, !values.f_NaN

values = fltarr(nodeList->getLength())

for i = 0, nodeList->getLength() - 1 do begin

    domNode = nodeList->item(i)

    child = domNode->getFirstChild()

    if obj_valid(child) then begin

        values[i] = float(child->getNodeValue())
    endif else begin

        values[i] = !values.f_NaN
    endelse
endfor

if n_elements(values) eq 1 then return, values[0] $
                           else return, values

end

;+ ; Converts the given Julian Day value to an ISO 8601 string ; representation. ; ; @private ; ; @param value {in} {type=julDay} ; Julian day value to convert. ; @returns ISO 8601 string representation of the given value ;- function SpdfRest::julDay2Iso8601, $ value compile_opt idl2

caldat, value, month, day, year, hour, minute, second

return, string(year, month, day, hour, minute, second, $
               format=self.iso8601Format)

end

;+ ; Creates a SpdfTimeInterval object from a child TimeInterval element ; of the given node from a cdas:DataResult XML document. ; ; @private ; ; @param domNode {in} {type=IDLffXMLDOMNode} ; node from a cdas:DataResult XML document. ; @returns a reference to a SpdfTimeInterval object. ;- function SpdfRest::getTimeIntervalChild, $ domNode compile_opt idl2

timeInterval = obj_new()

timeIntervalElements = domNode->getElementsByTagName('TimeInterval')

if timeIntervalElements->getLength() gt 0 then begin

    timeInterval = $
        self->getTimeInterval(timeIntervalElements->item(0))
end

return, timeInterval

end

;+ ; Creates a SpdfTimeInterval object from the given TimeInterval element ; from a cdas:DataResult XML document. ; ; @private ; ; @param timeIntervalElement {in} {type=IDLffXMLDOMNode} ; element from a cdas:DataResult XML document. ; @returns a reference to a SpdfTimeInterval object. ;- function SpdfRest::getTimeInterval, $ timeIntervalElement compile_opt idl2

startDate = $
    self->getJulDate((timeIntervalElement->$
                      getElementsByTagName('Start'))->item(0))

endDate = $
    self->getJulDate((timeIntervalElement->$
                      getElementsByTagName('End'))->item(0))


return, obj_new('SpdfTimeInterval', startDate, endDate)

end

;+ ; Creates a julday object from the given time element from a ; cdas:DataResult XML document. ; ; @private ; ; @param dateTimeElement {in} {type=IDLffXMLDOMNodeList} ; list whose first child is to be converted into a julday ; @returns julday representation of first child of given ; dateTimeElement. ;- function SpdfRest::getJulDate, $ dateTimeElement compile_opt idl2

dateFormat='(I4, 1X, I2, 1X, I2, 1X, I2, 1X, I2, 1X, I2)'

dateTimeStr = (dateTimeElement->getFirstChild())->getNodeValue()

reads, dateTimeStr, format=dateFormat, $
        year, month, day, hour, minute, second

return, julday(month, day, year, hour, minute, second)

end

;+ ; Perform an HTTP GET request to the given URL. This method provides ; functionality similar to doing ; obj_new(‘IDLffXMLDOMDocument’, filename=url) ; except that this method will catch and attempt to deal with errors. ; ; @private ; ; @param url {in} {type=string} ; URL of GET request to make. ; @keyword errorReporter {in} {optional} {type=string} ; name of IDL procedure to call if an HTTP error occurs. ; @returns reference to IDLffXMLDOMDocument representation of HTTP ; response entity. ;- function SpdfRest::makeGetRequest, url, url, errorReporter = errorReporter compile_opt idl2

username = ''
password = ''
retries = 0

catch, errorStatus
if (errorStatus ne 0) then begin

    reply = $
        self->handleHttpError( $
            requestUrl, errorReporter = errorReporter)

    obj_destroy, requestUrl

    if reply eq 0 || retries gt self.retryLimit then begin

        catch, /cancel
        return, obj_new()
    endif

    retries = retries + 1

endif

requestUrl = self->getRequestUrl(url, username, password)

result = string(requestUrl->get(/buffer))

obj_destroy, requestUrl

return, obj_new('IDLffXMLDOMDocument', string=result)

end

;+ ; Perform an HTTP POST request to the given URL. ; ; @private ; ; @param url {in} {type=string} ; URL of GET request to make. ; @param xmlRequest {in} {type=string} ; XML entity body to be include in the request. ; @keyword errorReporter {in} {optional} {type=string} ; name of IDL procedure to call if an HTTP error occurs. ; @returns reference to IDLffXMLDOMDocument representation of HTTP ; response entity. ;- function SpdfRest::makePostRequest, url,xmlRequest, url, xmlRequest, errorReporter = errorReporter compile_opt idl2

username = ''
password = ''
retries = 0

catch, errorStatus
if (errorStatus ne 0) then begin

    reply = $
        self->handleHttpError( $
            requestUrl, $
            errorReporter = errorReporter)

    obj_destroy, requestUrl

    if reply eq 0 || retries gt self.retryLimit then begin

        catch, /cancel
        return, obj_new()
    endif

    retries = retries + 1

endif

requestUrl = self->getRequestUrl(url, username, password)

requestUrl->setProperty, header='Content-Type: application/xml'

; print, ‘POSTing ’, xmlRequest ; print, ‘to ’, url

result = requestUrl->put(xmlRequest, /buffer, /post, url=url)

obj_destroy, requestUrl

return, obj_new('IDLffXMLDOMDocument', filename=result)

end

;+ ; Function to handle HTTP request errors. ; If an errorReporter has been provided, it is called. ; ; @private ; ; @param request {in} {type=IDLnetURL} ; HTTP request that caused the error. ; @keyword errorReporter {in} {optional} {type=string} ; name of IDL procedure to call if an HTTP error occurs. ; @returns a value of 1 if corrective action has occurred and a value of ; 0 if not. The corrective action for a 429/503 is waiting the ; specified time. The corrective action for a 408 is to wait 30 ; seconds. ;- function SpdfRest::handleHttpError, request, request, errorReporter = errorReporter compile_opt idl2

request->getProperty, $
    response_code=responseCode, $
    response_header=responseHeader, $
    response_filename=responseFilename

case responseCode of
    429 || 503: begin

        retryAfter = stregex(responseHeader, $
                             'Retry-After: ([0-9]+)' + string(13b), $
                             /extract, /subexpr)

        if n_elements(retryAfter) eq 2 && $
           strlen(retryAfter[1]) gt 0 then begin

            wait, fix(retryAfter[1])
        endif
    end
    408: begin

        wait, 30
    end
    else: begin

        if keyword_set(errorReporter) then begin

            call_method, 'reportError', errorReporter, $
                    responseCode, responseHeader, responseFilename
        endif

        return, 0
    end
endcase

return, 1

end

;+ ; Create an IDLnetUrl object from the given URL with any supplied ; authentication values set. ; ; @private ; ; @param url {in} {type=string} ; URL. ; @param username {in} {type=string} ; username. ; @param password {in} {type=string} ; password. ; @returns reference to a IDLnetUrl with any supplied authentication ; values set. ;- function SpdfRest::getRequestUrl, $ url, username, password compile_opt idl2

requestUrl = $
    obj_new('IDLnetURL', $
            proxy_authentication = self.proxy_authentication, $
            proxy_hostname = self.proxy_hostname, $
            proxy_port = self.proxy_port, $
            proxy_username = self.proxy_username, $
            proxy_password = self.proxy_password, $
            ssl_verify_peer = self.ssl_verify_peer)

urlComponents = parse_url(url)

requestUrl->setProperty, $
    header=self.userAgent, $
    url_scheme=urlComponents.scheme, $
    url_host=urlComponents.host, $
    url_port=urlComponents.port, $
    url_path=urlComponents.path, $
    url_query=urlComponents.query

if username ne '' then begin

    requestUrl->setProperty, $
        authentication=3, $
        url_username=username, $
        url_password=password
endif

return, requestUrl

end

;+ ; Defines the SpdfRest class. ; ; @field endpoint URL of SSC web service. ; @field userAgent HTTP ; <a href=“http://tools.ietf.org/html/rfc2616#section-14.43”> ; user-agent value</a> to use in communications with SSC. ; @field version identifies the version of this class. ; @field currentVersionUrl URL to the file identifying the most up to ; date version of this class. ; @field proxy_authentication IDLnetURL PROXY_AUTHENTICATION property ; value. ; @field proxy_hostname IDLnetURL PROXY_HOSTNAME property value. ; @field proxy_password IDLnetURL PROXY_PASSWORD property value. ; @field proxy_port IDLnetURL PROXY_PORT property value. ; @field proxy_username IDLnetURL PROXY_USERNAME property value. ; @field ssl_verify_peer IDLnetURL SSL_VERIFY_PEER property value. ; @field retryLimit retry limit for requests that fail with an http ; status of 429 or 503 with a Retry-After header. ;- pro SpdfRest__define compile_opt idl2 struct = { SpdfRest, endpoint:, endpoint:'', userAgent:”, version:, version:'', currentVersionUrl:”, proxyauthentication:0, proxy_authentication:0, proxy_hostname:”, proxypassword:, proxy_password:'', proxy_port:”, proxyusername:, proxy_username:'', ssl_verify_peer:1, retryLimit:50 retryLimit:50 } end