[java-support] 09/21: DDF-backed HttpServletRequest implementation.

Scott Cantor cantor.2 at osu.edu
Thu Jun 2 14:38:56 UTC 2022


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch dev/JSPT-111
in repository java-support.

View the commit online:
http://git.shibboleth.net/view/?p=java-support.git;a=commit;h=77ec7c7cb2e61c34cce1ece069be127f734eebda

commit 77ec7c7cb2e61c34cce1ece069be127f734eebda
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Sep 14 12:38:24 2021 -0400

    DDF-backed HttpServletRequest implementation.
---
 .../shibboleth/utilities/java/support/ddf/DDF.java |   9 +-
 .../support/ddf/RemotedHttpServletRequest.java     | 607 +++++++++++++++++++++
 2 files changed, 611 insertions(+), 5 deletions(-)

diff --git a/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java b/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java
index 53764c1..14a29c7 100644
--- a/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java
+++ b/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java
@@ -33,7 +33,6 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.NotThreadSafe;
 
-import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
@@ -802,12 +801,12 @@ public class DDF implements Iterable<DDF> {
      * @return immutable map, or null if the node is not a structure
      */
     @SuppressWarnings("unchecked")
-    @Nullable @NonnullElements @Unmodifiable @NotLive public Map<String,DDF> asMap() {
+    @Nonnull @NonnullElements @Unmodifiable @NotLive public Map<String,DDF> asMap() {
         if (isstruct()) {
             return Map.copyOf((Map<String,DDF>) value);
         }
         
-        return null;
+        return Collections.emptyMap();
     }
 
     /**
@@ -816,14 +815,14 @@ public class DDF implements Iterable<DDF> {
      * @return immutable list, or null if the node is not a structure or list
      */
     @SuppressWarnings("unchecked")
-    @Nullable @NonnullElements @Unmodifiable @NotLive public List<DDF> asList() {
+    @Nonnull @NonnullElements @Unmodifiable @NotLive public List<DDF> asList() {
         if (isstruct()) {
             return List.copyOf(((Map<String,DDF>) value).values());
         } else if (islist()) {
             return List.copyOf((List<DDF>) value);
         }
         
-        return null;
+        return Collections.emptyList();
     }
 
     /**
diff --git a/src/main/java/net/shibboleth/utilities/java/support/ddf/RemotedHttpServletRequest.java b/src/main/java/net/shibboleth/utilities/java/support/ddf/RemotedHttpServletRequest.java
new file mode 100644
index 0000000..ee779e6
--- /dev/null
+++ b/src/main/java/net/shibboleth/utilities/java/support/ddf/RemotedHttpServletRequest.java
@@ -0,0 +1,607 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.utilities.java.support.ddf;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.ReadListener;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.net.URISupport;
+
+/**
+ * Wraps a {@link DDF} object reflecting a remoted message encapsulating an HTTP request.
+ * 
+ * <p>Potential TODOs are noted in various places. One outstanding issue is header case.
+ * The SP code never did case-folding of the header names, so we may need to adjust that
+ * on the C++ side to force-lower them in transit and then lower them on access.</p>
+ * 
+ */
+ at NotThreadSafe
+public class RemotedHttpServletRequest implements HttpServletRequest {
+
+    /** Empty byte array for empty bodies. */
+    @Nonnull private static final byte[] EMPTY_BODY = new byte[0];
+    
+    /** Underlying object containing remoted data. */
+    @Nonnull private final DDF obj;
+    
+    /** Cookie array. */
+    @Nullable @NonnullElements private ArrayList<Cookie> cookies;
+    
+    /** Parameter map. */
+    @Nullable private Map<String, String[]> parameters;
+    
+    /**
+     * Constructor.
+     *
+     * @param ddf remoted request information
+     */
+    public RemotedHttpServletRequest(@Nonnull final DDF ddf) {
+        obj = Constraint.isNotNull(ddf, "DDF cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    public Object getAttribute(final String name) {
+        // TODO: might use this to expose certain "standard" pieces of information
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration<String> getAttributeNames() {
+        // TODO: adjust if we support the method above.
+        return Collections.emptyEnumeration();
+    }
+
+    /** {@inheritDoc} */
+    public String getCharacterEncoding() {
+        return "UTF-8";
+    }
+
+    /** {@inheritDoc} */
+    public void setCharacterEncoding(final String env) throws UnsupportedEncodingException {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public int getContentLength() {
+        final Integer i = obj.getmember("content_length").integer();
+        return i != null ? i : -1;
+    }
+
+    /** {@inheritDoc} */
+    public long getContentLengthLong() {
+        return getContentLength();
+    }
+
+    /** {@inheritDoc} */
+    public String getContentType() {
+        return obj.getmember("content_type").string();
+    }
+
+    /** {@inheritDoc} */
+    public ServletInputStream getInputStream() throws IOException {
+        final String body = obj.getmember("body").string();
+        if (body != null) {
+            // The body is always assumed to be safely encoded for our use cases.
+            return new BodyInputStream(body.getBytes("UTF-8"));
+        }
+        return new BodyInputStream(EMPTY_BODY);
+    }
+
+    /** {@inheritDoc} */
+    public String getParameter(final String name) {
+        final String[] values = getParameterMap().get(name);
+        if (values != null) {
+            return values[0];
+        }
+        
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration<String> getParameterNames() {
+        return Collections.enumeration(getParameterMap().keySet());
+    }
+
+    /** {@inheritDoc} */
+    public String[] getParameterValues(final String name) {
+        return getParameterMap().get(name);
+    }
+
+    /** {@inheritDoc} */
+    public Map<String, String[]> getParameterMap() {
+        if (parameters != null) {
+            parameters = new HashMap<>();
+            final Multimap<String,String> multimap = ArrayListMultimap.create();
+            final String qs = getQueryString();
+            if (qs != null) {
+                final List<Pair<String,String>> qparams = URISupport.parseQueryString(qs);
+                for (final Pair<String,String> p : qparams) {
+                    multimap.put(p.getFirst(), p.getSecond());
+                }
+            }
+            
+            if ("application/x-www-form-urlencoded".equals(getContentType())) {
+                final String body = obj.getmember("body").string();
+                if (body != null) {
+                    final List<Pair<String,String>> qparams = URISupport.parseQueryString(body);
+                    for (final Pair<String,String> p : qparams) {
+                        multimap.put(p.getFirst(), p.getSecond());
+                    }
+                }
+            }
+            
+            for (final Map.Entry<String,Collection<String>> entry : multimap.asMap().entrySet()) {
+                parameters.put(entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()]));
+            }
+        }
+        
+        return parameters;
+    }
+
+    /** {@inheritDoc} */
+    public String getProtocol() {
+        final String protocol = obj.getmember("protocol").string();
+        return protocol != null ? protocol : "HTTP/1.1";
+    }
+
+    /** {@inheritDoc} */
+    public String getScheme() {
+        return obj.getmember("scheme").string();
+    }
+
+    /** {@inheritDoc} */
+    public String getServerName() {
+        return obj.getmember("hostname").string();
+    }
+
+    /** {@inheritDoc} */
+    public int getServerPort() {
+        final Integer i = obj.getmember("port").integer();
+        return i != null ? i : -1;
+    }
+
+    /** {@inheritDoc} */
+    public BufferedReader getReader() throws IOException {
+        return new BufferedReader(new StringReader(obj.getmember("body").string()));
+    }
+
+    /** {@inheritDoc} */
+    public String getRemoteAddr() {
+        return obj.getmember("client_addr").string();
+    }
+
+    /** {@inheritDoc} */
+    public String getRemoteHost() {
+        return obj.getmember("client_addr").string();
+    }
+
+    /** {@inheritDoc} */
+    public void setAttribute(final String name, final Object o) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public void removeAttribute(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public Locale getLocale() {
+        throw new UnsupportedOperationException();        
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration<Locale> getLocales() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSecure() {
+        return "https".equals(getScheme());
+    }
+
+    /** {@inheritDoc} */
+    public RequestDispatcher getRequestDispatcher(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public String getRealPath(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public int getRemotePort() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public String getLocalName() {
+        // TODO: If we need this, should be configurable via the c'tor.
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public String getLocalAddr() {
+        // TODO: If we need this, should be configurable via the c'tor.
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public int getLocalPort() {
+        // TODO: If we need this, should be configurable via the c'tor.
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public ServletContext getServletContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public AsyncContext startAsync() throws IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public AsyncContext startAsync(final ServletRequest servletRequest, final ServletResponse servletResponse)
+            throws IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isAsyncStarted() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isAsyncSupported() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public AsyncContext getAsyncContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public DispatcherType getDispatcherType() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public String getAuthType() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public Cookie[] getCookies() {
+        if (cookies == null) {
+            final String header = getHeader("Cookie");
+            if (header != null) {
+                final String[] carray = header.split(";");
+                if (carray != null) {
+                    cookies = new ArrayList<>(carray.length);
+                    for (final String c : carray) {
+                        final String[] nvpair = c.split("=", -1);
+                        if (nvpair.length == 2) {
+                            final String name = nvpair[0].trim();
+                            // This is a fallback cookie used for Safari to work around SameSite bugs.
+                            if (name.endsWith("_fgwars")) {
+                                name.substring(0, name.length() - 7);
+                            }
+                            cookies.add(new Cookie(name, nvpair[1]));
+                        }
+                    }
+                }
+            }
+        }
+        return cookies.toArray(new Cookie[cookies.size()]);
+    }
+
+    /** {@inheritDoc} */
+    public long getDateHeader(final String name) {
+        final DDF h = obj.getmember("headers").getmember(name);
+        if (h.isstring()) {
+            try {
+                // TODO: there are a ton of valid formats but I don't think we really will need this anyway.
+                final SimpleDateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
+                formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
+                final Date d = formatter.parse(h.string());
+                if (d != null) {
+                    return d.getTime();
+                }
+            } catch (final Exception e) {
+                // Ignore for now.
+            }
+        }
+        return -1;
+    }
+
+    /** {@inheritDoc} */
+    public String getHeader(final String name) {
+        return obj.getmember("headers").getmember(name).string();
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration<String> getHeaders(final String name) {
+        final String s = obj.getmember("headers").getmember(name).string();
+        if (s != null) {
+            return Collections.enumeration(Collections.singletonList(s));
+        }
+        return Collections.emptyEnumeration();
+    }
+
+    /** {@inheritDoc} */
+    public Enumeration<String> getHeaderNames() {
+        return Collections.enumeration(obj.getmember("headers").asMap().keySet());
+    }
+
+    /** {@inheritDoc} */
+    public int getIntHeader(final String name) {
+        final DDF h = obj.getmember("headers").getmember(name);
+        if (h.isstring()) {
+            return Integer.parseInt(h.string());
+        }
+        return -1;
+    }
+
+    /** {@inheritDoc} */
+    public String getMethod() {
+        return obj.getmember("method").string();
+    }
+
+    /** {@inheritDoc} */
+    public String getPathInfo() {
+        // TODO: If we need this, should be configurable via the c'tor.
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public String getPathTranslated() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public String getContextPath() {
+        // TODO: If we need this, should be configurable via the c'tor.
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public String getQueryString() {
+        return obj.getmember("query").string();
+    }
+
+    /** {@inheritDoc} */
+    public String getRemoteUser() {
+        return obj.getmember("remote_user").string();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isUserInRole(final String role) {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public Principal getUserPrincipal() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public String getRequestedSessionId() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public String getRequestURI() {
+        return obj.getmember("uri").string();
+    }
+
+    /** {@inheritDoc} */
+    public StringBuffer getRequestURL() {
+        return new StringBuffer(obj.getmember("url").string());
+    }
+
+    /** {@inheritDoc} */
+    public String getServletPath() {
+        // TODO: If we need this, should be configurable via the c'tor.
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public HttpSession getSession(final boolean create) {
+        if (create) {
+            throw new UnsupportedOperationException();
+        }
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public HttpSession getSession() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public String changeSessionId() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isRequestedSessionIdValid() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isRequestedSessionIdFromCookie() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isRequestedSessionIdFromURL() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isRequestedSessionIdFromUrl() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean authenticate(final HttpServletResponse response) throws IOException, ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public void login(final String username, final String password) throws ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    public void logout() throws ServletException {
+        
+    }
+
+    /** {@inheritDoc} */
+    public Collection<Part> getParts() throws IOException, ServletException {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public Part getPart(final String name) throws IOException, ServletException {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public <T extends HttpUpgradeHandler> T upgrade(final Class<T> handlerClass) throws IOException, ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    /** Helper class cribbed from Spring. */
+    private static class BodyInputStream extends ServletInputStream {
+
+        /** Underlying stream. */
+        @Nonnull private final InputStream delegate;
+
+        public BodyInputStream(final byte[] body) {
+            delegate = new ByteArrayInputStream(body);
+        }
+
+        @Override
+        public boolean isFinished() {
+            return false;
+        }
+
+        @Override
+        public boolean isReady() {
+            return true;
+        }
+
+        @Override
+        public void setReadListener(final ReadListener readListener) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int read() throws IOException {
+            return delegate.read();
+        }
+
+        @Override
+        public int read(final byte[] b, final int off, final int len) throws IOException {
+            return delegate.read(b, off, len);
+        }
+
+        @Override
+        public int read(final byte[] b) throws IOException {
+            return delegate.read(b);
+        }
+
+        @Override
+        public long skip(final long n) throws IOException {
+            return delegate.skip(n);
+        }
+
+        @Override
+        public int available() throws IOException {
+            return delegate.available();
+        }
+
+        @Override
+        public void close() throws IOException {
+            delegate.close();
+        }
+
+        @Override
+        public synchronized void mark(final int readlimit) {
+            delegate.mark(readlimit);
+        }
+
+        @Override
+        public synchronized void reset() throws IOException {
+            delegate.reset();
+        }
+
+        @Override
+        public boolean markSupported() {
+            return delegate.markSupported();
+        }
+    }
+
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list