DEV Community

João Paulo Martins Silva
João Paulo Martins Silva

Posted on

A Quine that is also an HTTP server.

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));
 }
}`
Enter fullscreen mode Exit fullscreen mode

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;
            }
        }
                                        """;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Image description

Image description

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)