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;
}
@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