I've always been fascinated by the concept of a Quine. I decided to implement my own version, only a bit different. To recap, a Quine is a program whose output is its own source code. For example, as in this project on GitHub implementing a Quine in various versions of Java:
An example of a Quine in Java:
public class Quine {
public static void main(String[] args) {
String textBlockQuotes = new String(new char[]{'"', '"', '"'});
char newLine = 10;
String source = """
public class Quine {
public static void main(String[] args) {
String textBlockQuotes = new String(new char[]{'"', '"', '"'});
char newLine = 10;
String source = %s;
System.out.print(source.formatted(textBlockQuotes + newLine + source + textBlockQuotes));
}
}
""";
System.out.print(source.formatted(textBlockQuotes + newLine + source + textBlockQuotes));
}
}`
My project aims to create an HTTP server encapsulated in a JAR file that, upon receiving a request, generates a new JAR containing its own source code. This new JAR, when executed, starts a replica of the original server, thus creating a self-replicating server.
Initially, the code generates a Jar containing the program's own compiled source code. To do this, a String containing it is first created.
String code = """
package com.server.quine;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
public class QuineServer {
public static void main(String[] args) throws IOException {
// Cria um arquivo JAR
ByteArrayOutputStream jarOutputStream = createJarFile();
// Cria o socket do servidor
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
// Espera por uma conexão
Socket socket = serverSocket.accept();
System.out.println("Aceitou a conexão");
// Escreve a resposta
try (OutputStream output = socket.getOutputStream()) {
// Escreve cabeçalhos HTTP para download do arquivo
PrintWriter writer = new PrintWriter(output, true);
writer.println("HTTP/1.1 200 OK");
writer.println("Server: Java HTTP Server: 1.0");
writer.println("Date: " + new java.util.Date());
writer.println("Content-type: application/java-archive");
writer.println("Content-length: " + jarOutputStream.toByteArray().length);
writer.println("Content-Disposition: attachment; filename=QuineServer.jar");
writer.println(); // Linha em branco entre os cabeçalhos e o conteúdo
writer.flush();
output.write(jarOutputStream.toByteArray());
output.flush();
}
// Fecha a conexão
socket.close();
}
}
private static ByteArrayOutputStream createJarFile() throws IOException {
String source = buildSourceCode();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
File tempDir = Files.createTempDirectory("test").toFile();
File sourceFile = new File(tempDir, "test/QuineServer.java");
sourceFile.getParentFile().mkdirs();
Files.write(sourceFile.toPath(), source.getBytes(StandardCharsets.UTF_8));
compiler.run(null, null, null, "-d", tempDir.getAbsolutePath(), sourceFile.getPath());
File classFile = new File(tempDir, "com/server/quine/QuineServer.class");
byte[] classBytes = Files.readAllBytes(classFile.toPath());
// Cria o Manifesto
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, "com.server.quine.QuineServer");
ByteArrayOutputStream jarOutputStream = new ByteArrayOutputStream();
JarOutputStream jarOut = new JarOutputStream(jarOutputStream, manifest);
// Adiciona a classe ao JAR
JarEntry classEntry = new JarEntry("com/server/quine/QuineServer.class");
jarOut.putNextEntry(classEntry);
jarOut.write(classBytes);
jarOut.closeEntry();
// Fecha o JarOutputStream
jarOut.close();
return jarOutputStream;
}
private static String buildSourceCode() {
String textBlockQuotes = new String(new char[]{'"', '"', '"'});
char newLine = 10;
String code = %s;
String formatedCode = code.formatted( textBlockQuotes + newLine + code + textBlockQuotes);
return formatedCode;
}
}
""";
This same code contains a parameter in which its own content is passed. Thus, a source code containing itself recursively will always be generated.
The formatted method of the String class takes care of this.
String formatedCode = code.formatted(textBlockQuotes + newLine + code + textBlockQuotes);
Now that the String containing the source code has been created, it will be used to generate a file in a temporary folder. This file is the QuineServer.java class, that is, the very class that generated it.
File sourceFile = new File(tempDir, "test/QuineServer.java");
sourceFile.getParentFile().mkdirs();
Files.write(sourceFile.toPath(), source.getBytes(StandardCharsets.UTF_8));
To compile this source code dynamically within the program, the JavaCompiler class is used.
In this section, the result of the compilation, the .class file, is generated inside the temporary folder.
compiler.run(null, null, null, "-d", tempDir.getAbsolutePath(), sourceFile.getPath());
The content of the new file is stored in a variable.
File classFile = new File(tempDir, "com/server/quine/QuineServer.class");
byte[] classBytes = Files.readAllBytes(classFile.toPath());
With the code compiled, it's time to create the Jar. The following code generates a Manifest file and packages the compiled code.
// Cria o Manifesto
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, "com.server.quine.QuineServer");
ByteArrayOutputStream jarOutputStream = new ByteArrayOutputStream();
JarOutputStream jarOut = new JarOutputStream(jarOutputStream, manifest);
// Adiciona a classe ao JAR
JarEntry classEntry = new JarEntry("com/server/quine/QuineServer.class");
jarOut.putNextEntry(classEntry);
jarOut.write(classBytes);
jarOut.closeEntry();
// Fecha o JarOutputStream
jarOut.close();
The code starts a server on port 8080. This server waits for incoming connections. When a connection is established, it responds by sending a JAR file, which was dynamically created by the program itself.
while (true) {
// Espera por uma conexão
Socket socket = serverSocket.accept();
System.out.println("Aceitou a conexão");
// Escreve a resposta
try (OutputStream output = socket.getOutputStream()) {
// Escreve cabeçalhos HTTP para download do arquivo
PrintWriter writer = new PrintWriter(output, true);
writer.println("HTTP/1.1 200 OK");
writer.println("Server: Java HTTP Server: 1.0");
writer.println("Date: " + new java.util.Date());
writer.println("Content-type: application/java-archive");
writer.println("Content-length: " + jarOutputStream.toByteArray().length);
writer.println("Content-Disposition: attachment; filename=QuineServer.jar");
writer.println(); // Linha em branco entre os cabeçalhos e o conteúdo
writer.flush();
output.write(jarOutputStream.toByteArray());
output.flush();
}
// Fecha a conexão
socket.close();
}
To test the self-replicating server, I created a script that runs the original JAR server, downloads the generated JAR, and repeats the process with the new JAR. This continuous loop results in the creation of multiple servers and JAR files.
#!/usr/bin/bash
# URL do arquivo JAR
JAR_URL="localhost:8080"
JAR_NAME="QuineServer.jar"
# Executa o arquivo JAR em segundo plano e armazena o PID
java -jar "$JAR_NAME" & JAR_PID=$!
sleep 1
while true; do
# Gera um nome de arquivo baseado na data e hora atual ou extrai da URL
JAR_NAME="arquivo_$(date +%Y%m%d%H%M%S).jar"
# Ou use: JAR_NAME=$(basename "$JAR_URL")
# Baixa o arquivo JAR
echo "Iniciando o download de $JAR_URL..."
wget -O "$JAR_NAME" "$JAR_URL"
if [ $? -eq 0 ]; then
echo "Download concluído com sucesso. Executando $JAR_NAME em segundo plano..."
# Interrompe o processo do arquivo JAR
echo "Interrompendo o processo do JAR (PID: $JAR_PID)."
kill $JAR_PID
# Executa o arquivo JAR em segundo plano e armazena o PID
java -jar "$JAR_NAME" &
JAR_PID=$!
# Aguarda um determinado período antes de interromper o processo do JAR. Exemplo: 60 segundos
sleep 1
echo "Processo do JAR interrompido. Aguardando para a próxima execução..."
else
echo "Falha no download do arquivo. Tentando novamente..."
fi
# Aguarda novamente antes de repetir o processo, se necessário
sleep 1
done
If you found this article interesting, and for some reason, you want to fill your HD with self-replicating jars, I've made the code available on my GitHub: .
Any suggestions or ideas, feel free to make a pull request or get in touch.
Top comments (0)