Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 31 additions & 25 deletions src/graphtea/extensions/AlgorithmUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ public static void resetVertexMarks(GraphModel g) {
public static boolean isConnected(GraphModel g) {
ArrayList<Integer> vs = new ArrayList<>();
int[] parent = new int[g.getVerticesCount()];
for(int i=0;i < g.getVerticesCount();i++) parent[i] = -1;
for (int i = 0; i < g.getVerticesCount(); i++) {
parent[i] = -1;
}
dfs(g, 0, vs, parent);
return Arrays.stream(vs.toArray()).distinct().toArray().length == g.getVerticesCount();
return vs.stream().distinct().count() == g.getVerticesCount();
}

/**
Expand Down Expand Up @@ -428,17 +430,19 @@ public static String getEigenValues(GraphModel g) {
EigenvalueDecomposition ed = A.eig();
double[] rv = ed.getRealEigenvalues();
double[] iv = ed.getImagEigenvalues();
String res = "";
StringBuilder res = new StringBuilder();
for (int i = 0; i < rv.length; i++) {
if (iv[i] != 0)
res +="" + AlgorithmUtils.round(rv[i],10) + " + " + AlgorithmUtils.round(iv[i],10) + "i";
else
res += "" + AlgorithmUtils.round(rv[i],10);
if(i!=rv.length-1) {
res += ",";
if (iv[i] != 0) {
res.append(AlgorithmUtils.round(rv[i], 10)).append(" + ")
.append(AlgorithmUtils.round(iv[i], 10)).append("i");
} else {
res.append(AlgorithmUtils.round(rv[i], 10));
}
if (i != rv.length - 1) {
res.append(",");
}
}
return res;
return res.toString();
}

/**
Expand Down Expand Up @@ -495,25 +499,27 @@ public static String getEigenValues(Matrix A) {
EigenvalueDecomposition ed = A.eig();
double[] rv = ed.getRealEigenvalues();
double[] iv = ed.getImagEigenvalues();
String res = "";
List<Double> EigenValues = new ArrayList<>();
StringBuilder res = new StringBuilder();
List<Double> eigenValues = new ArrayList<>();
for (int i = 0; i < rv.length; i++) {
if (iv[i] != 0)
res +="" + AlgorithmUtils.round(rv[i],10) + " + " + AlgorithmUtils.round(iv[i],10) + "i";
else
EigenValues.add(AlgorithmUtils.round(rv[i],10));
}
if(EigenValues.size() > 0) {
res = "";
EigenValues.sort((aDouble, t1) -> -aDouble.compareTo(t1));
for (int i = 0; i < EigenValues.size(); i++) {
res += EigenValues.get(i);
if(i != EigenValues.size() - 1) {
res+=",";
if (iv[i] != 0) {
res.append(AlgorithmUtils.round(rv[i], 10)).append(" + ")
.append(AlgorithmUtils.round(iv[i], 10)).append("i");
} else {
eigenValues.add(AlgorithmUtils.round(rv[i], 10));
}
}
if (!eigenValues.isEmpty()) {
res = new StringBuilder();
eigenValues.sort((a, b) -> -a.compareTo(b));
for (int i = 0; i < eigenValues.size(); i++) {
res.append(eigenValues.get(i));
if (i != eigenValues.size() - 1) {
res.append(",");
}
}
}
return res;
return res.toString();
}

// get kth minimum degree
Expand Down
14 changes: 7 additions & 7 deletions src/graphtea/extensions/io/GraphSaveObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import graphtea.graph.graph.GraphModel;
import graphtea.graph.graph.Vertex;

import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.util.Vector;
import java.util.ArrayList;
import java.util.Base64;

/**
* Created by rostam on 18.12.15.
Expand All @@ -22,8 +22,8 @@ public boolean equals(Object obj) {
} else return false;
}

public Vector<VertexSaveObject> vs = new Vector<>();
Vector<EdgeSaveObject> es = new Vector<>();
public ArrayList<VertexSaveObject> vs = new ArrayList<>();
ArrayList<EdgeSaveObject> es = new ArrayList<>();
Comment on lines +25 to +26

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Old .tea files unreadable 🐞 Bug ✓ Correctness

GraphSaveObject is serialized as the on-disk GraphTea format (*.tea); changing its serialized field
types from Vector to ArrayList will prevent loading graphs saved by older versions (deserialization
will fail due to incompatible field metadata). This breaks backward compatibility for
SaveGraph/LoadGraph.
Agent Prompt
### Issue description
`GraphSaveObject` is persisted to disk via Java serialization for the GraphTea `*.tea` format. The PR changes the serialized field types (`Vector` -> `ArrayList`) without providing any migration logic, which will make older `*.tea` files fail to deserialize.

### Issue Context
`SaveGraph` writes `new GraphSaveObject(graph)` through `ObjectOutputStream`, and `LoadGraph` reads it back through `ObjectInputStream`.

### Fix Focus Areas
- src/graphtea/extensions/io/GraphSaveObject.java[16-36]
- src/graphtea/extensions/io/SaveGraph.java[27-34]
- src/graphtea/extensions/io/LoadGraph.java[35-42]

### Implementation direction
- Add explicit serialization compatibility in `GraphSaveObject`:
  - Use `serialPersistentFields` and `readObject`/`writeObject` to persist the legacy schema (e.g., store as `Vector` in the stream) while using `ArrayList` in memory, **or** read both possible types (`Vector` or `ArrayList`) and convert.
  - Keep stream field names stable (`vs`, `es`, etc.) and convert during `readObject`.
- Validate by loading a `.tea` file saved with the previous version.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

boolean directed = false;
String label = "";
public GraphSaveObject(GraphModel g) {
Expand Down Expand Up @@ -71,15 +71,15 @@ public static byte[] getBytesOfGraphSaveObject(GraphSaveObject gso) {
}

public static String graph2String(GraphModel g){
return DatatypeConverter.printBase64Binary(getBytesOfGraph(g));
return Base64.getMimeEncoder().encodeToString(getBytesOfGraph(g));
}

public static GraphModel String2Graph(String s){
return getGraphFromBytes(DatatypeConverter.parseBase64Binary(s));
return getGraphFromBytes(Base64.getMimeDecoder().decode(s));
}

public static GraphSaveObject string2GraphSaveObject(String s){
return getGraphSaveOobjectfromBytes(DatatypeConverter.parseBase64Binary(s));
return getGraphSaveOobjectfromBytes(Base64.getMimeDecoder().decode(s));
}

public static GraphSaveObject getGraphSaveOobjectfromBytes(byte[] b){
Expand Down
4 changes: 2 additions & 2 deletions src/graphtea/graph/old/ArrowHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ public GraphPreferences GraphPrefFactory() {
return new GraphPreferences(this.getClass().getSimpleName(), this, "Graph Drawings");
}

public HashMap<Object, ArrayX> defineEligibleValuesForSettings(HashMap<Object, ArrayX> objectValues) {
ArrayX t = new ArrayX(3);
public HashMap<Object, ArrayX<?>> defineEligibleValuesForSettings(HashMap<Object, ArrayX<?>> objectValues) {
ArrayX<Integer> t = new ArrayX<>(3);
t.addValidValue(10);
t.addValidValue(20);
t.addValidValue(30);
Expand Down
85 changes: 38 additions & 47 deletions src/graphtea/platform/extension/ExtensionClassLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
// Distributed under the terms of the GNU General Public License (GPL): http://www.gnu.org/licenses/
package graphtea.platform.extension;

import java.util.ArrayList;
import java.util.List;
import java.util.ArrayList;
import java.util.List;
import graphtea.platform.StaticUtils;
import graphtea.platform.core.exception.ExceptionHandler;

Expand All @@ -22,7 +22,7 @@
*/
public class ExtensionClassLoader extends ClassLoader {
public Map<String, byte[]> classesData = new HashMap<>();
Map<String, Class> classes = new HashMap<>();
Map<String, Class<?>> classes = new HashMap<>();
List<File> unknownFiles = new ArrayList<>();
public static URLClassLoader classLoader;
public static ClassLoader cl;
Expand All @@ -31,7 +31,7 @@ public ExtensionClassLoader(String dirPath) {
loadClasses(dirPath);
}

public Collection<Class> getLoadedClasses() {
public Collection<Class<?>> getLoadedClasses() {
return classes.values();
}

Expand Down Expand Up @@ -60,14 +60,16 @@ private void loadClassFiles(String dir, String pack) {
else if (file1.getName().endsWith(".class"))
try {
String name;
if (pack.length() > 0)
if (pack.length() > 0) {
name = pack.substring(1) + ".";
else
} else {
name = "";
}
name = name + file1.getName().substring(0, file1.getName().length() - 6);
byte[] data = new byte[(int) file1.length()];
FileInputStream fis = new FileInputStream(file1);
fis.read(data);
try (FileInputStream fis = new FileInputStream(file1)) {
fis.read(data);
}
classesData.put(name, data);
urls.add(file1.toURI().toURL());
} catch (IOException e) {
Expand All @@ -85,26 +87,28 @@ private void loadClasses(String dirPath) {
// defineClasses();
}

public Class findClass(String name) throws ClassNotFoundException {
Class ret = null;
if (!classesData.containsKey(name))
public Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
if (!classesData.containsKey(name)) {
ret = getParent().loadClass(name);
}
if (ret == null && !classes.containsKey(name)) {
byte[] data = classesData.get(name);
Class c = defineClass(name, data, 0, data.length);
Class<?> c = defineClass(name, data, 0, data.length);
classes.put(name, c);
}
ret = classes.get(name);
if (ret == null)
if (ret == null) {
return classLoader.loadClass(name);
else
} else {
return ret;
}
}

public Collection<Class> getClassesImplementing(Class cl) {
Collection<Class> col = new ArrayList<>();
for (Map.Entry<String, Class> entry1 : classes.entrySet()) {
Class c = entry1.getValue();
public Collection<Class<?>> getClassesImplementing(Class<?> cl) {
Collection<Class<?>> col = new ArrayList<>();
for (Map.Entry<String, Class<?>> entry1 : classes.entrySet()) {
Class<?> c = entry1.getValue();
if (StaticUtils.isImplementing(c, cl))
col.add(c);
}
Expand All @@ -131,36 +135,23 @@ public static void copyInputStream(InputStream in, OutputStream out)
* @param zipFileName The given zipped file name
*/
public static void unZip(String zipFileName, String destDir) {
System.out.println(zipFileName);
Enumeration entries;
ZipFile zipFile;

try {
zipFile = new ZipFile(zipFileName);

entries = zipFile.entries();

while(entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry)entries.nextElement();

if(entry.isDirectory()) {
// Assume directories are stored parents first then children.
// System.err.println("Extracting directory: " + entry.getName());
// This is not robust, just for demonstration purposes.
(new File(destDir+File.separator+entry.getName())).mkdirs();
continue;
}

System.out.println("Extracting file: " + destDir+entry.getName());
copyInputStream(zipFile.getInputStream(entry),
new BufferedOutputStream(new FileOutputStream(destDir+File.separator+entry.getName())));
try (ZipFile zipFile = new ZipFile(zipFileName)) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.isDirectory()) {
new File(destDir + File.separator + entry.getName()).mkdirs();
continue;
}
try (InputStream in = zipFile.getInputStream(entry);
OutputStream out = new BufferedOutputStream(
new FileOutputStream(destDir + File.separator + entry.getName()))) {
copyInputStream(in, out);
Comment on lines +146 to +149

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Zip path traversal extraction 🐞 Bug ⛨ Security

ExtensionClassLoader.unZip writes zip entries to destDir + File.separator + entry.getName()
without normalizing/validating the entry path, allowing a crafted extension zip to write outside
destDir via ../ (Zip Slip). This is particularly risky because loadClassFiles automatically unzips
every *.zip in the extensions directory.
Agent Prompt
### Issue description
`ExtensionClassLoader.unZip` uses `entry.getName()` directly when creating output `File`s. A malicious zip entry name (e.g., `../outside.txt`) can cause writes outside the target directory (Zip Slip).

### Issue Context
`loadClassFiles` auto-unzips every `*.zip` under the extensions directory.

### Fix Focus Areas
- src/graphtea/platform/extension/ExtensionClassLoader.java[50-55]
- src/graphtea/platform/extension/ExtensionClassLoader.java[137-155]

### Implementation direction
- Use `java.nio.file.Path`:
  - `Path dest = Paths.get(destDir).toRealPath();`
  - `Path target = dest.resolve(entry.getName()).normalize();`
  - Reject entries where `!target.startsWith(dest)`.
- For file entries:
  - `Files.createDirectories(target.getParent());`
  - Write to `target.toFile()`.
- Apply the same containment check for directory entries before `createDirectories`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

}
}
} catch (IOException ioe) {
ExceptionHandler.catchException(ioe);
}

zipFile.close();
} catch (IOException ioe) {
System.err.println("Unhandled exception:");
ExceptionHandler.catchException(ioe);
}
}


Expand Down
4 changes: 2 additions & 2 deletions src/graphtea/platform/preferences/AbstractPreference.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public AbstractPreference(String name, Preferences pref, String category) {

public NotifiableAttributeSetImpl attributeSet = new NotifiableAttributeSetImpl();

protected void putAttribute(String name, ArrayX values) {
protected void putAttribute(String name, ArrayX<?> values) {
attributeSet.put(name, values);
}

Expand All @@ -45,7 +45,7 @@ protected <T> T getAttribute(String name) {
}


public abstract void defineAttributes(HashMap<Object, ArrayX> objectValues);
public abstract void defineAttributes(HashMap<Object, ArrayX<?>> objectValues);


public abstract void defineListeners(AttributeListener al);
Expand Down
6 changes: 3 additions & 3 deletions src/graphtea/platform/preferences/GraphPreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public GraphPreferences(String name, HashSet<Object> oneInstances, String catego
defineListeners(this);
}

public void defineAttributes(HashMap<Object, ArrayX> objectValues) {
public void defineAttributes(HashMap<Object, ArrayX<?>> objectValues) {

for (Object o : objectValues.keySet()) {
putAttribute(o.toString(), objectValues.get(o));
Expand All @@ -50,10 +50,10 @@ public void defineAttributes(HashMap<Object, Object> objectValues, boolean t) {
}
}

public void defineMultipleAttributes(HashMap<Object, HashMap<Object, ArrayX>> map) {
public void defineMultipleAttributes(HashMap<Object, HashMap<Object, ArrayX<?>>> map) {

for (Object o : map.keySet()) {
HashMap<Object, ArrayX> hashMap = map.get(o);
HashMap<Object, ArrayX<?>> hashMap = map.get(o);
for (Object fields : hashMap.keySet()) {
putAttribute(o.toString() + "*" + fields.toString(), hashMap.get(fields));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
public interface UserDefinedEligiblity {
GraphPreferences GraphPrefFactory();

HashMap<Object, ArrayX> defineEligibleValuesForSettings(HashMap<Object, ArrayX> objectValues);
HashMap<Object, ArrayX<?>> defineEligibleValuesForSettings(HashMap<Object, ArrayX<?>> objectValues);
}
Loading