You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2022/07/24 10:43:42 UTC

[commons-jexl] branch master updated: JEXL-376: ensure a class is exported before adding it to ClassMap; - added ClassTool utility class to capture Java9+ 'backport' of essential methods;

This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new a641dc43 JEXL-376: ensure a class is exported before adding it to ClassMap; - added ClassTool utility class to capture Java9+ 'backport' of essential methods;
a641dc43 is described below

commit a641dc4325a4526c537bf142e2983ef3c3b9ea84
Author: henrib <he...@apache.org>
AuthorDate: Sun Jul 24 12:43:30 2022 +0200

    JEXL-376: ensure a class is exported before adding it to ClassMap;
    - added ClassTool utility class to capture Java9+ 'backport' of essential methods;
---
 .../jexl3/internal/introspection/ClassMap.java     |  12 +-
 .../jexl3/internal/introspection/ClassTool.java    | 131 +++++++++++++++++++++
 .../jexl3/internal/introspection/Permissions.java  |  70 +----------
 .../jexl3/internal/introspection/Uberspect.java    |   2 +-
 .../java/org/apache/commons/jexl3/ScriptTest.java  | 100 ++++++++++++++--
 .../internal/introspection/PermissionsTest.java    |  12 +-
 src/test/scripts/httpPost.jexl                     |  53 +++++++++
 7 files changed, 286 insertions(+), 94 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
index 2be0b0f4..ba68aaf0 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
@@ -110,7 +110,7 @@ final class ClassMap {
             }
             @Override
             public Set<Entry<MethodKey, Method>> entrySet() {
-                return null;
+                return Collections.emptySet();
             }
             @Override public Method get(Object name) {
                 return CACHE_MISS;
@@ -221,11 +221,7 @@ final class ClassMap {
                 if (methodList != null) {
                     cacheEntry = methodKey.getMostSpecificMethod(methodList);
                 }
-                if (cacheEntry == null) {
-                    byKey.put(methodKey, CACHE_MISS);
-                } else {
-                    byKey.put(methodKey, cacheEntry);
-                }
+                byKey.put(methodKey, cacheEntry == null? CACHE_MISS : cacheEntry);
             } catch (final MethodKey.AmbiguousException ae) {
                 // that's a miss :-)
                 byKey.put(methodKey, CACHE_MISS);
@@ -248,7 +244,7 @@ final class ClassMap {
      */
     private static void create(final ClassMap cache, final JexlPermissions permissions, Class<?> clazz, final Log log) {
         //
-        // Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start
+        // Build a list of all elements in the class hierarchy. This one is bottom-first; we start
         // with the actual declaring class and its interfaces and then move up (superclass etc.) until we
         // hit java.lang.Object. That is important because it will give us the methods of the declaring class
         // which might in turn be abstract further up the tree.
@@ -256,7 +252,7 @@ final class ClassMap {
         // We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions.
         //
         for (Class<?> classToReflect = clazz; classToReflect != null; classToReflect = classToReflect.getSuperclass()) {
-            if (Modifier.isPublic(classToReflect.getModifiers())) {
+            if (Modifier.isPublic(classToReflect.getModifiers()) && ClassTool.isExported(classToReflect)) {
                 populateWithClass(cache, permissions, classToReflect, log);
             }
             final Class<?>[] interfaces = classToReflect.getInterfaces();
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassTool.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassTool.java
new file mode 100644
index 00000000..fb531b35
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassTool.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.commons.jexl3.internal.introspection;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+/**
+ * Utility for Java9+ backport in Java8 of class and module related methods.
+ */
+class ClassTool {
+    /** The Class.getModule() method. */
+    private static final MethodHandle GET_MODULE;
+    /** The Class.getPackageName() method. */
+    private static final MethodHandle GET_PKGNAME;
+    /** The Module.isExported(String packageName) method. */
+    private static final MethodHandle IS_EXPORTED;
+    static {
+        final MethodHandles.Lookup LOOKUP= MethodHandles.lookup();
+        MethodHandle getModule = null;
+        MethodHandle getPackageName = null;
+        MethodHandle isExported = null;
+        try {
+            Class<?> modulec = ClassTool.class.getClassLoader().loadClass("java.lang.Module");
+            if (modulec != null) {
+                getModule = LOOKUP.findVirtual(Class.class, "getModule", MethodType.methodType(modulec));
+                if (getModule != null) {
+                    getPackageName = LOOKUP.findVirtual(Class.class, "getPackageName", MethodType.methodType(String.class));
+                    if (getPackageName != null) {
+                        isExported = LOOKUP.findVirtual(modulec, "isExported", MethodType.methodType(boolean.class, String.class));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // ignore all
+        }
+        GET_MODULE = getModule;
+        GET_PKGNAME = getPackageName;
+        IS_EXPORTED = isExported;
+    }
+
+    /**
+     * Checks whether a class is exported by its module (java 9+).
+     * The code performs the following sequence through reflection (since the same jar can run
+     * on a Java8 or Java9+ runtime and the module features does not exist on 8).
+     * <code>
+     * Module module = declarator.getModule();
+     * return module.isExported(declarator.getPackageName());
+     * </code>
+     * This is required since some classes and methods may not be exported thus not callable through
+     * reflection.
+     * @param declarator the class
+     * @return true if class is exported or no module support exists
+     */
+    static boolean isExported(Class<?> declarator) {
+        if (IS_EXPORTED != null)  {
+            try {
+                final Object module = GET_MODULE.invoke(declarator);
+                if (module != null) {
+                    final String pkgName = (String) GET_PKGNAME.invoke(declarator);
+                    return (Boolean) IS_EXPORTED.invoke(module, pkgName);
+                }
+            } catch (Throwable e) {
+                // ignore
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Gets the package name of a class (class.getPackage() may return null).
+     * @param clz the class
+     * @return the class package name
+     */
+    static String getPackageName(Class<?> clz) {
+        String pkgName = "";
+        if (clz != null) {
+            // use native if we can
+            if (GET_PKGNAME != null) {
+                try {
+                    return (String) GET_PKGNAME.invoke(clz);
+                } catch(Throwable xany) {
+                    return "";
+                }
+            }
+            // remove array
+            Class<?> clazz = clz;
+            while(clazz.isArray()) {
+                clazz = clazz.getComponentType();
+            }
+            // mimic getPackageName()
+            if (clazz.isPrimitive()) {
+                return "java.lang";
+            }
+            // remove enclosing
+            Class<?> walk = clazz.getEnclosingClass();
+            while(walk != null) {
+                clazz = walk;
+                walk = walk.getEnclosingClass();
+            }
+            Package pkg = clazz.getPackage();
+            // pkg may be null for unobvious reasons
+            if (pkg == null) {
+                String name = clazz.getName();
+                int dot = name.lastIndexOf('.');
+                if (dot > 0) {
+                    pkgName = name.substring(0, dot);
+                }
+            } else {
+                pkgName = pkg.getName();
+            }
+        }
+        return pkgName;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
index 11342571..582bce3e 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
@@ -17,8 +17,6 @@
 
 package org.apache.commons.jexl3.internal.introspection;
 
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
@@ -52,21 +50,6 @@ import org.apache.commons.jexl3.introspection.JexlPermissions;
  * not be altered using an instance of permissions using {@link JexlPermissions#parse(String...)}.</p>
  */
 public class Permissions implements JexlPermissions {
-    /**
-     * Java9 introduced Class.getPackageName(), use it if it exists.
-     */
-    private static final MethodHandle GETPKGNAME = getPackageNameHandle();
-    static MethodHandle getPackageNameHandle() {
-        MethodHandle mh;
-        try {
-            Method m = Class.class.getMethod("getPackageName");
-            mh = MethodHandles.lookup().unreflect(m);
-        } catch(Exception xm) {
-            mh = null;
-        }
-        return mh;
-    }
-
     /**
      * Equivalent of @NoJexl on a class in a package.
      */
@@ -260,53 +243,6 @@ public class Permissions implements JexlPermissions {
         return allowed == null? Collections.emptySet() : Collections.unmodifiableSet(allowed);
     }
 
-
-    /**
-     * Gets the package name of a class (class.getPackage() may return null).
-     * @param clz the class
-     * @return the class package name
-     */
-    static String getPackageName(Class<?> clz) {
-        String pkgName = "";
-        if (clz != null) {
-            // use native if we can
-            if (GETPKGNAME != null) {
-                try {
-                    return (String) GETPKGNAME.invokeWithArguments(clz);
-                } catch(Throwable xany) {
-                    return "";
-                }
-            }
-            // remove array
-            Class<?> clazz = clz;
-            while(clazz.isArray()) {
-                clazz = clazz.getComponentType();
-            }
-            // mimic getPackageName()
-            if (clazz.isPrimitive()) {
-                return "java.lang";
-            }
-            // remove enclosing
-            Class<?> walk = clazz.getEnclosingClass();
-            while(walk != null) {
-                clazz = walk;
-                walk = walk.getEnclosingClass();
-            }
-            Package pkg = clazz.getPackage();
-            // pkg may be null for unobvious reasons
-            if (pkg == null) {
-                String name = clazz.getName();
-                int dot = name.lastIndexOf('.');
-                if (dot > 0) {
-                    pkgName = name.substring(0, dot);
-                }
-            } else {
-                pkgName = pkg.getName();
-            }
-        }
-        return pkgName;
-    }
-
     /**
      * Gets the package constraints.
      * @param packageName the package name
@@ -324,7 +260,7 @@ public class Permissions implements JexlPermissions {
      * @return the class constraints instance, not-null.
      */
     private NoJexlClass getNoJexl(Class<?> clazz) {
-        String pkgName = getPackageName(clazz);
+        String pkgName = ClassTool.getPackageName(clazz);
         NoJexlPackage njp = getNoJexlPackage(pkgName);
         if (njp != null) {
             NoJexlClass njc = njp.getNoJexl(clazz);
@@ -341,7 +277,7 @@ public class Permissions implements JexlPermissions {
      * @return true if allowed, false otherwise
      */
     private boolean wildcardAllow(Class<?> clazz) {
-        return wildcardAllow(allowed, getPackageName(clazz));
+        return wildcardAllow(allowed, ClassTool.getPackageName(clazz));
     }
 
     /**
@@ -393,7 +329,7 @@ public class Permissions implements JexlPermissions {
         if (nojexl != null) {
             return true;
         }
-        NoJexlPackage njp = packages.get(getPackageName(clazz));
+        NoJexlPackage njp = packages.get(ClassTool.getPackageName(clazz));
         return njp != null && Objects.equals(NOJEXL_CLASS, njp.getNoJexl(clazz));
     }
 
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
index 589a84d7..8e8c3e37 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
@@ -69,7 +69,7 @@ public class Uberspect implements JexlUberspect {
     /**
      * The map from arithmetic classes to overloaded operator sets.
      * <p>
-     * This keeps track of which operator methods are overloaded per JexlArithemtic classes
+     * This map keeps track of which operator methods are overloaded per JexlArithmetic classes
      * allowing a fail fast test during interpretation by avoiding seeking a method when there is none.
      */
     private final Map<Class<? extends JexlArithmetic>, Set<JexlOperator>> operatorMap;
diff --git a/src/test/java/org/apache/commons/jexl3/ScriptTest.java b/src/test/java/org/apache/commons/jexl3/ScriptTest.java
index 6c7ed1b3..fb9f0e65 100644
--- a/src/test/java/org/apache/commons/jexl3/ScriptTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ScriptTest.java
@@ -16,8 +16,14 @@
  */
 package org.apache.commons.jexl3;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
 import java.net.URL;
+
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -25,10 +31,11 @@ import org.junit.Test;
  * Tests for JexlScript
  * @since 1.1
  */
-@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+@SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"})
 public class ScriptTest extends JexlTestCase {
     static final String TEST1 =  "src/test/scripts/test1.jexl";
     static final String TEST_ADD =  "src/test/scripts/testAdd.jexl";
+    static final String TEST_JSON =  "src/test/scripts/httpPost.jexl";
 
     // test class for testScriptUpdatesContext
     // making this class private static will cause the test to fail.
@@ -55,7 +62,7 @@ public class ScriptTest extends JexlTestCase {
      * Test creating a script from spaces.
      */
     @Test
-    public void testSpacesScript() throws Exception {
+    public void testSpacesScript() {
         final String code = " ";
         final JexlScript s = JEXL.createScript(code);
         Assert.assertNotNull(s);
@@ -65,37 +72,106 @@ public class ScriptTest extends JexlTestCase {
      * Test creating a script from a string.
      */
     @Test
-    public void testSimpleScript() throws Exception {
+    public void testSimpleScript() {
         final String code = "while (x < 10) x = x + 1;";
         final JexlScript s = JEXL.createScript(code);
         final JexlContext jc = new MapContext();
-        jc.set("x", new Integer(1));
+        jc.set("x",1);
 
         final Object o = s.execute(jc);
-        Assert.assertEquals("Result is wrong", new Integer(10), o);
+        Assert.assertEquals("Result is wrong", 10, o);
         Assert.assertEquals("getText is wrong", code, s.getSourceText());
     }
 
     @Test
-    public void testScriptFromFile() throws Exception {
+    public void testScriptJsonFromFileJexl() {
+        final File testScript = new File(TEST_JSON);
+        final JexlScript s = JEXL.createScript(testScript);
+        final JexlContext jc = new MapContext();
+        jc.set("httpr", new HttpPostRequest());
+        Object result = s.execute(jc);
+        Assert.assertNotNull(result);
+        Assert.assertEquals("{  \"id\": 101}", result);
+    }
+
+    @Test
+    public void testScriptJsonFromFileJava() {
+        final String testScript ="httpr.execute('https://jsonplaceholder.typicode.com/posts', null)";
+        final JexlScript s = JEXL.createScript(testScript);
+        final JexlContext jc = new MapContext();
+        jc.set("httpr", new HttpPostRequest());
+        Object result = s.execute(jc);
+        Assert.assertNotNull(result);
+        Assert.assertEquals("{  \"id\": 101}", result);
+    }
+
+    /**
+     * An object to call from.
+     */
+    public static class HttpPostRequest {
+        public static String execute(String url, String data) throws IOException {
+            return httpPostRequest(url, data);
+        }
+    }
+
+    /**
+     *  HTTP post.
+     * @param sURL the url
+     * @param jsonData some json data
+     * @return the result
+     * @throws IOException
+     */
+    private static String httpPostRequest(String sURL, String jsonData) throws IOException {
+        URL url = new java.net.URL(sURL);
+        HttpURLConnection con = (HttpURLConnection) url.openConnection();
+        con.setRequestMethod("POST");
+        con.setRequestProperty("Accept", "application/json");
+        // send data
+        if ( jsonData != null ) {
+            con.setRequestProperty("Content-Type", "application/json; utf-8");
+            con.setDoOutput(true);
+
+            OutputStream outputStream = con.getOutputStream();
+            byte[] input = jsonData.getBytes("utf-8");
+            outputStream.write(input, 0, input.length);
+        }
+        // read response
+        int responseCode = con.getResponseCode();
+        InputStream inputStream = null;
+        inputStream =  con.getInputStream();
+        StringBuffer response = new java.lang.StringBuffer();
+        if (inputStream != null) {
+            try (BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream))) {
+                String inputLine = "";
+                while ((inputLine = in.readLine()) != null) {
+                    response.append(inputLine);
+                }
+            }
+        }
+        return response.toString();
+    }
+
+
+    @Test
+    public void testScriptFromFile() {
         final File testScript = new File(TEST1);
         final JexlScript s = JEXL.createScript(testScript);
         final JexlContext jc = new MapContext();
         jc.set("out", System.out);
         final Object result = s.execute(jc);
         Assert.assertNotNull("No result", result);
-        Assert.assertEquals("Wrong result", new Integer(7), result);
+        Assert.assertEquals("Wrong result", 7, result);
     }
 
     @Test
-    public void testArgScriptFromFile() throws Exception {
+    public void testArgScriptFromFile() {
         final File testScript = new File(TEST_ADD);
         final JexlScript s = JEXL.createScript(testScript,"x", "y");
         final JexlContext jc = new MapContext();
         jc.set("out", System.out);
         final Object result = s.execute(jc, 13, 29);
         Assert.assertNotNull("No result", result);
-        Assert.assertEquals("Wrong result", new Integer(42), result);
+        Assert.assertEquals("Wrong result", 42, result);
     }
 
     @Test
@@ -106,7 +182,7 @@ public class ScriptTest extends JexlTestCase {
         jc.set("out", System.out);
         final Object result = s.execute(jc);
         Assert.assertNotNull("No result", result);
-        Assert.assertEquals("Wrong result", new Integer(7), result);
+        Assert.assertEquals("Wrong result", 7, result);
     }
 
     @Test
@@ -117,11 +193,11 @@ public class ScriptTest extends JexlTestCase {
         jc.set("out", System.out);
         final Object result = s.execute(jc, 13, 29);
         Assert.assertNotNull("No result", result);
-        Assert.assertEquals("Wrong result", new Integer(42), result);
+        Assert.assertEquals("Wrong result", 42, result);
     }
 
     @Test
-    public void testScriptUpdatesContext() throws Exception {
+    public void testScriptUpdatesContext() {
         final String jexlCode = "resultat.setCode('OK')";
         final JexlExpression e = JEXL.createExpression(jexlCode);
         final JexlScript s = JEXL.createScript(jexlCode);
diff --git a/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
index fe8d9c8b..d516601c 100644
--- a/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
+++ b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
@@ -188,19 +188,19 @@ public class PermissionsTest {
     @Test
     public void testGetPackageName() {
         final String PKG = "org.apache.commons.jexl3.internal.introspection";
-        String pkg = Permissions.getPackageName(Outer.class);
+        String pkg = ClassTool.getPackageName(Outer.class);
         Assert.assertEquals(PKG, pkg);
-        pkg = Permissions.getPackageName(Outer.Inner.class);
+        pkg = ClassTool.getPackageName(Outer.Inner.class);
         Assert.assertEquals(PKG, pkg);
         Outer[] oo = new Outer[0];
-        pkg = Permissions.getPackageName(oo.getClass());
+        pkg = ClassTool.getPackageName(oo.getClass());
         Assert.assertEquals(PKG, pkg);
         Outer.Inner[] ii = new Outer.Inner[0];
-        pkg = Permissions.getPackageName(ii.getClass());
+        pkg = ClassTool.getPackageName(ii.getClass());
         Assert.assertEquals(PKG, pkg);
-        pkg = Permissions.getPackageName(Process.class);
+        pkg = ClassTool.getPackageName(Process.class);
         Assert.assertEquals("java.lang", pkg);
-        pkg = Permissions.getPackageName(Integer.TYPE);
+        pkg = ClassTool.getPackageName(Integer.TYPE);
         Assert.assertEquals("java.lang", pkg);
     }
 
diff --git a/src/test/scripts/httpPost.jexl b/src/test/scripts/httpPost.jexl
new file mode 100644
index 00000000..5f106203
--- /dev/null
+++ b/src/test/scripts/httpPost.jexl
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+//-------------------------------------------------------------------
+// send a POST Request
+//-------------------------------------------------------------------
+
+var httpPostRequest = (sURL, jsonData) -> {
+  var url = new("java.net.URL", sURL);
+  var con = url.openConnection();
+  con.setRequestMethod("POST");
+  con.setRequestProperty("Accept", "application/json");
+
+  // send data
+  if ( jsonData != null ) {
+    con.setRequestProperty("Content-Type", "application/json; utf-8");
+    con.setDoOutput(true);
+
+    var outputStream = con.getOutputStream();
+    var input = jsonData.getBytes("utf-8");
+    outputStream.write(input, 0, size(input));
+  }
+
+  // read response
+  var responseCode = con.getResponseCode();
+  var inputStream = null;
+  inputStream =  con.getInputStream();
+  var response = new("java.lang.StringBuffer");
+  if (inputStream != null) {
+    var in = new("java.io.BufferedReader", new("java.io.InputStreamReader", inputStream));
+    var inputLine = "";
+    while ((inputLine = in.readLine()) != null) {
+      response.append(inputLine);
+    }
+    in.close();
+  }
+  response.toString();
+}
+
+httpPostRequest("https://jsonplaceholder.typicode.com/posts");