Skip to content

Instantly share code, notes, and snippets.

@gmencz
Last active May 13, 2023 18:38
Show Gist options
  • Save gmencz/7d6973b8ab95f5adae1739e88f956f70 to your computer and use it in GitHub Desktop.
Save gmencz/7d6973b8ab95f5adae1739e88f956f70 to your computer and use it in GitHub Desktop.
type CookieOptions = {
maxAge?: number;
expires?: Date;
httpOnly?: boolean;
path?: string;
domain?: string;
secure?: boolean;
sameSite?: boolean | 'lax' | 'strict' | 'none';
};
type ParsedCookie = {
cookieName: string;
cookieValue: string;
options?: CookieOptions;
};
function hasExpiresField(cookie: string): boolean {
return cookie
.split(';')
.map(part => {
if (part.split('=').length > 1) {
return part.split('=')[0].trim().toLowerCase() === 'expires';
}
return false;
})
.some(bool => bool);
}
export default function parseCookies(rawCookies: string): ParsedCookie[] {
// Make this check more effective
if (!rawCookies.includes('=')) {
throw new Error(
'Invalid raw cookies, look at the format of a set-cookie header string and provide something similar.'
);
}
/*
Cookie format: "name=value; Path=/; HttpOnly; Secure"
Multiple cookies format: "name=value; Path=/; HttpOnly; Secure, name2=value2"
*/
const arraifyedRawCookies = rawCookies.split(',');
const validRawCookies = arraifyedRawCookies
.map((rawCookie, index, ref) => {
if (hasExpiresField(rawCookie)) {
return `${rawCookie}${ref[index + 1]}`;
}
if (index > 0 && hasExpiresField(ref[index - 1])) return 'invalid';
return rawCookie;
})
.filter(rawCookie => rawCookie !== 'invalid');
const parsedCookies = validRawCookies.map(rawCookie => {
const [cookieNameAndValue, ...cookieProperties] = rawCookie.split(';');
const [cookieName, cookieValue] = cookieNameAndValue.split('=');
const sanitizedCookieProperties = cookieProperties.map(cookieProperty => {
const [propertyName, propertyValue] = cookieProperty.split('=');
const sanitizedPropertyName = propertyName
.replace('-', '')
.toLowerCase()
.trim();
if (sanitizedPropertyName === 'maxage') {
return `maxAge=${propertyValue}`;
} else if (sanitizedPropertyName === 'httponly') {
return 'httpOnly=true';
} else if (sanitizedPropertyName === 'samesite') {
return `sameSite=${propertyValue.toLowerCase()}`;
}
if (!propertyValue) {
return `${sanitizedPropertyName}=true`;
}
return `${sanitizedPropertyName}=${propertyValue}`;
});
const objectifyedCookieProperties = sanitizedCookieProperties.map(
sanitizedCookieProperty => {
const [propertyName, propertyValue] = sanitizedCookieProperty.split(
'='
);
return {
[propertyName]: propertyValue === 'true' ? true : propertyValue,
};
}
);
const options: CookieOptions = {};
objectifyedCookieProperties.forEach(objectifyedCookieProperty => {
Object.entries(objectifyedCookieProperty).forEach(([key, value]) => {
if (key === 'expires') {
options[key] = new Date(value as string);
return;
}
if (key === 'maxAge') {
options[key] = Number(value as string);
return;
}
options[key] = value;
});
});
return {
cookieName: cookieName.trim(),
cookieValue: cookieValue.trim(),
options,
};
});
return parsedCookies;
}
@JamesDelfini
Copy link

JamesDelfini commented Jul 4, 2020

Thank you for your for this cookie parser and the solution of the federation issue setting of cookies with the services at Apollo federation cookies issue.

@Nighthawk14
Copy link

Hey, thanks for the piece of code. The splitCookiesString method of this library seems to do the same thing btw:
https://github.com/nfriedly/set-cookie-parser#splitcookiesstringcombinedsetcookieheader

@AhmedBHameed
Copy link

Finally!! find a script to parse cookies correctly :) Thanks for sharing.

Coming from this thread apollographql/apollo-server#3099 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment