Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
@Internal
public class NettyCookie implements Cookie {

private final io.netty.handler.codec.http.cookie.Cookie nettyCookie;
private transient io.netty.handler.codec.http.cookie.Cookie nettyCookie;

/**
* @param nettyCookie The Netty cookie
Expand Down Expand Up @@ -170,4 +170,64 @@ public int compareTo(Cookie o) {
NettyCookie nettyCookie = (NettyCookie) o;
return nettyCookie.nettyCookie.compareTo(this.nettyCookie);
}

private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
out.writeUTF(nettyCookie.name());
out.writeUTF(nettyCookie.value());
out.writeLong(nettyCookie.maxAge());
out.writeBoolean(nettyCookie.isHttpOnly());
out.writeBoolean(nettyCookie.isSecure());

out.writeBoolean(nettyCookie.domain() != null);
if (nettyCookie.domain() != null) {
out.writeUTF(nettyCookie.domain());
}

out.writeBoolean(nettyCookie.path() != null);
if (nettyCookie.path() != null) {
out.writeUTF(nettyCookie.path());
}

io.netty.handler.codec.http.cookie.CookieHeaderNames.SameSite sameSite = null;
if (nettyCookie instanceof DefaultCookie defaultCookie) {
sameSite = defaultCookie.sameSite();
}
out.writeBoolean(sameSite != null);
if (sameSite != null) {
out.writeUTF(sameSite.name());
}
}

private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
String name = in.readUTF();
String value = in.readUTF();
long maxAge = in.readLong();
boolean httpOnly = in.readBoolean();
boolean secure = in.readBoolean();

String domain = null;
if (in.readBoolean()) {
domain = in.readUTF();
}

String path = null;
if (in.readBoolean()) {
path = in.readUTF();
}

io.netty.handler.codec.http.cookie.CookieHeaderNames.SameSite sameSite = null;
if (in.readBoolean()) {
sameSite = io.netty.handler.codec.http.cookie.CookieHeaderNames.SameSite.valueOf(in.readUTF());
}

DefaultCookie newNettyCookie = new DefaultCookie(name, value);
newNettyCookie.setMaxAge(maxAge);
newNettyCookie.setHttpOnly(httpOnly);
newNettyCookie.setSecure(secure);
newNettyCookie.setDomain(domain);
newNettyCookie.setPath(path);
newNettyCookie.setSameSite(sameSite);

this.nettyCookie = newNettyCookie;
}
}
86 changes: 86 additions & 0 deletions reproduce_issue.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import groovy.transform.Field

@Field
String testSource = """
package io.micronaut.reproduce

import io.micronaut.http.netty.cookies.NettyCookie
import io.netty.handler.codec.http.cookie.DefaultCookie
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.io.NotSerializableException
import spock.lang.Specification

class ReproduceIssueSpec extends Specification {

def "NettyCookie should be serializable or throw NotSerializableException"() {
when: "a NettyCookie is created and serialization is attempted"
def nettyCookie = new NettyCookie(new DefaultCookie("mycookie", "myvalue"))

boolean serializedSuccessfully = false
try {
def baos = new ByteArrayOutputStream()
def oos = new ObjectOutputStream(baos)
oos.writeObject(nettyCookie)
oos.close()
baos.close()
serializedSuccessfully = true
} catch (NotSerializableException e) {
// This is the specific exception that indicates the bug is reproduced.
println ">>> Caught NotSerializableException: " + e.message
serializedSuccessfully = false
} catch (Exception e) {
// Catch any other unexpected exceptions
println ">>> Caught unexpected exception during serialization: " + e.message
e.printStackTrace()
serializedSuccessfully = false
}

then: "the NettyCookie can be serialized without NotSerializableException"
// This assertion should pass if the bug is fixed (serialization succeeds).
// This assertion should fail if the bug is reproduced (NotSerializableException is caught).
serializedSuccessfully == true
}
}
"""

def testDir = new File("test-suite/src/test/groovy/io/micronaut/reproduce")
def testFile = new File(testDir, "ReproduceIssueSpec.groovy")

try {
testDir.mkdirs()
testFile.write(testSource)

// Command to execute the specific Spock test
def command = "./gradlew :test-suite:test --tests io.micronaut.reproduce.ReproduceIssueSpec"
println "Executing command: $command"
def process = command.execute()
process.waitForProcessOutput(System.out, System.err)
def gradlewExitCode = process.exitValue()

// Logic to determine the final script exit code based on gradlew's exit code:
// If gradlewExitCode is 0: The Spock test passed. This means `serializedSuccessfully` was true.
// Thus, the bug is NOT reproduced (serialization succeeded).
// In this scenario, the outer script should exit with 0.
//
// If gradlewExitCode is non-zero (e.g., 1 for test failure): The Spock test failed. This means `serializedSuccessfully` was false.
// This indicates that `NotSerializableException` was caught,
// meaning the bug IS reproduced.
// In this scenario, the outer script should exit with 129.

if (gradlewExitCode == 0) {
println "Gradle test passed. This implies NettyCookie serialized successfully. Bug is NOT reproduced."
System.exit(0) // Issue not reproduced
} else {
println "Gradle test failed. This implies NettyCookie serialization failed (likely NotSerializableException). Bug IS reproduced."
System.exit(129) // Issue reproduced
}
} catch (Exception e) {
e.printStackTrace()
System.exit(1) // Script execution error
} finally {
// Clean up the created test file and directory
if (testDir.exists()) {
testDir.deleteDir() // This will recursively delete the directory and its contents
}
}