cookies and sessions are not being saved - php

my site is working (sort off). When i check if there sessions are there, they echo out a message which works BUT when i check session storage in chrome, the sessions are not coming up, which is strange. I have also tried to set a cookie but that is not coming up either. So what am i doing wrong. So the sessions are working but not getting stored, and the cookies are not getting stored either
this is part of login class
public function __construct(DB $pdo)
{
$this->pdo = $pdo->pdo;
if(isset($_GET['logout'])){
$_SESSION = array();
session_destroy();
}
}
public function checklogin()
{
if(isset($_SESSION['user_sess']) && $_SESSION['logged_in'] === true){
return true;
} else {
return false;
}
}
public function loginwithdata($email, $password)
{
$query = $this->pdo->prepare('SELECT * FROM `users` WHERE `email` = ?');
$query->bindValue(1, $email);
try{
$query->execute();
$data = $query->fetch();
$salt = $data['salt'];
$user_key = $data['user_key'];
$hashed_pass = sha1(md5($salt.$password));
if($this->verify($hashed_pass, $email) === true){
$_SESSION['user_sess'] = $user_key;
$_SESSION['logged_in'] = true;
setcookie('key', '12345678910', 1209600, '/');
return true;
} else {
return false;
}
} catch(PDOException $e) {
die($e->getMessage());
}
}
here is the ajax_login.php
require '../core/init.php';
if(isset($_POST))
{
$email = $_POST['email'];
$password = $_POST['password'];
if(!empty($email) && (!empty($password))){
$try = $login->loginwithdata($email, $password);
if($try){
//login successful
echo 'success';
} else {
echo 'login failed';
}
}
}
and on my index page i have
require_once 'core/init.php';
if($login->checklogin() === true){
echo "you are logged in";
} else if ($login->checklogin() === false) {
echo "you are not logged in";
}
and my init file
session_start();
error_reporting(E_ALL);
date_default_timezone_set('Europe/London');
require_once 'classes/DB.php';
require_once 'classes/Upload.php';
require_once 'classes/Login.php';
require_once 'classes/Register.php';
require_once 'classes/Site.php';
require_once 'classes/Admin.php';
require_once 'sinitize.php';
$pdo = new DB;
$upload = new Upload($pdo);
$login = new Login($pdo);
$register = new Register($pdo);

Your code looks good so far.
But wait.. dude.. Sessions generally get stored in a COOKIE (as ID). SESSION STORAGE and WEB STORAGE in chrome is something completely different and is sorta part of HTML5 rather than PHP Sessions.
You say you get the proper echoes so there is really nothing wrong with your session.
If you open the developers console and in networking tab you see the cookie sent, it's everything perfect.
If you are having problems with the session cookie itself,
please provide and check the session configuration variables from php.ini:
From console:
php -i | grep session
or use phpinfo(); in a web served script.
session.use_cookies should be On
See: http://www.php.net/manual/de/ini.list.php

Some browsers, if path is set, wants the domain too:
setcookie ( $name, $value, $expire, $path, $domain);
About $expire
It's the "absolute" time in seconds since Epoc when the cookie expire, so expire within an hour should be:
$expire = time()+3600;
see also:
http://www.php.net/setcookie

Related

PHP $_SESSION variable not working first time

$_SESSION['isloggedin'] doesn't seem to be working on first load.
This only happens on server, not on localhost.
session_start() is at the top of each page.
initialized to: $_SESSION['isloggedin'] = false;
When user logs in $_SESSION['isloggedin'] = true;
When user logs out $_SESSION['isloggedin'] = false;
on home.php:
if (!$_SESSION['isloggedin']) {
die(header("Location: login.php"));
}
on login.php:
if ($_SESSION['isloggedin']) {
die(header("Location: home.php"));
}
When you login and sent to the home page $_SESSION['isloggedin'] doesn't seem to be true so it redirects to login.php. But since it is true it redirects to Home.php causing a redirect loop.
when a redirect loop error pops up, I refresh and am taken to the right page. Sometimes the page self refreshes and takes me to the correct page, still showing redirect error before.
Why isn't $_SESSION variable working properly on server? The correct value doesn't seem to register the first time on every page, every site link.
EDIT:
everything works as expected on localhost just not on the online server.
when login is clicked and everything passes the class login function is called:
class users {
$_SESSION['isLoggedIn'] = false;
function __construct() {
if (session_id() == "") {
session_start();
}
if (isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] == true) {
if (session_id() == '') {
session_start();
}
}
}
function login($user,$password) {
if (session_id() == "") {
session_start();
}
$_SESSION['isLoggedIn'] = false;
$mysqli = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_DATABASE);
if ($mysqli->connect_errno) {
return false;
}
$user = $mysqli->real_escape_string($user);
$password = $mysqli->real_escape_string($password);
$query = "SELECT * from users WHERE email=$user";
if (!$result = $mysqli->query($query)) {
return false;
}
$row = $result->fetch_assoc();
$db_pass = $row['password'];
if (crypt($password,$db_pass) != $db_pass) {
return false;
}
$_SESSION['isLoggedIn'] = true;
if (session_id() == '') {
session_start();
}
return true;
}
}
Try changing your code to something like this
if (!isset($_SESSION['isloggedin'])) {
header("Location: login.php");
} else {
header("Location: home.php");
}
I was using AWS Elastic Beanstalk to run this web app. I didn't think this matter but apparently it did. It turns out that sessions don't work the same as they do on localhost since you are dividing your servers. I needed to enable sticky session within the load balancer.

PHP session not holding value on page change

I know there are quite a few posts about this topic but none of them have fixed my issue.
I have WAMP server running on my windows computer. I've created a login system that is able to set PHP sessions and verify that they are working while on the page that they were created. Once I change to a different page, either by typing in the URL or a javascript function I lose the session.
Here is my test.php that logs a user in
require_once('config.php');
$user = new User();
$result = $user->login('email#gmail.com', 'mysecretpassword');
echo $result;
echo '</br>';
$result = $user->isLoggedIn();
echo $result;
This is the function that actually logs the user in
public function login($email, $password) {
// Hash Password
$password = $this->hashPassword($password);
// Check if email and password match
$query = "SELECT id, confirm_email FROM users WHERE email = ? AND password = ?";
$a_bind_params = array($email, $password);
$a_param_types = array('s','s');
$results = $this->db->select($query, $a_bind_params, $a_param_types);
// If we didnt get a result then email/password must be wrong
if(count($results) == 0) return 1;
// Now check that they verrified their email
if($results[0]['confirm_email'] == 'N') return 2;
// User is real and everything is good
// Update login Date
$a_bind_params = array(date('Y-m-d H:i:s'), $results[0]['id']);
$a_param_types = array('s','s');
$query = "UPDATE users SET login_at = ? WHERE id = ?";
// There was a problem updating their login table so just fail the login
if(!$this->db->update($query, $a_bind_params, $a_param_types)) return 3;
// Login user
Session::set("user_id", $results[0]['id']);
session_regenerate_id(true);
Session::set("login_fingerprint", $this->_generateLoginString ());
return 0;
}
Here is the function that checks if the user is logged in
// Checks if user is logged in
public function isLoggedIn() {
//if $_SESSION['user_id'] is not set return false
if(Session::get("user_id") == null)
return false;
$loginString = $this->_generateLoginString();
$currentString = Session::get("login_fingerprint");
if($currentString != null && $currentString == $loginString)
return true;
else {
//destroy session, it is probably stolen by someone
$this->logout();
return false;
}
}
Here is the Session function that creates a session
public static function startSession() {
ini_set('session.use_only_cookies', true);
$cookieParams = session_get_cookie_params();
session_set_cookie_params(
$cookieParams["lifetime"],
$cookieParams["path"],
$cookieParams["domain"],
SESSION_SECURE,
SESSION_HTTP_ONLY
);
session_start();
session_regenerate_id(true);
}
These are the function that set/get the user session
public static function set($key, $value) {
$_SESSION[$key] = $value;
}
public static function get($key, $default = null) {
if(isset($_SESSION[$key]))
return $_SESSION[$key];
else
return $default;
}
Finally this is my config.php file that actually calls session_start() and include some constants
// REQUIRE ALL FILES
require_once("ClassSession.php");
require_once("ClassDatabase.php");
require_once("ClassUser.php");
Session::startSession();
If I navigate to another page called test.php
include "config.php";
$user = new User();
if($user->isLoggedIn()) echo 'logged in';
else 'Not logged in';
The session is lost and the user is not logged in anymore.
I've checked my sessions.save_path in PHP.ini and have checked the wamp64/temp folder and my sessions are being stored in there. I am also calling session_start() on every page because I am including config.php on both the test pages. Not sure why I am losing my sessions.
EDIT
I forgot to mention what is actually happening. When I login the user I look up their user_id from the database and store that into $_SESSION['user_id']. When I access $_SESSION[user_id] from the page that logged the user in I get back the correct value. However, when I change to another page $_SESSION['user_id'] is null.
UPDATE
session_regenerate_id(true) is not the problem.
Here are my Session settings when printing php_info()
session_set_cookie_params(
$cookieParams["lifetime"],
$cookieParams["path"],
$cookieParams["domain"],
SESSION_SECURE,
SESSION_HTTP_ONLY
);
Check values here. What is SESSION_SECURE const value? Maybe you are restricting cookie usage to https only and your site is on http.
I just wrote a very basic PHP code to give you an idea how this can be quickly built. You should change the parameters, use mysqli or PDO or ORM whichever you like but the basic idea remains the same. Use secure cookies, follow the best practices to hide what needs to be secured.
Create a php file call it functions.php and paste the following code in it
<?php
session_start();
function createsessions($email, $password, $loginId)
{
//Add additional member to Session array as per requirement
//session_register();
$_SESSION["email"] = $email;
$_SESSION["password"] = $password;
$_SESSION["loginId"] = $loginId;
//Add additional member to cookie array as per requirement
setcookie("loginEmail", $_SESSION['loginEmail'], time() + 31536000, "/", ".example.com", 0, true);
setcookie("password", $_SESSION['password'], time() + 31536000, "/", ".example.com", 0, true);
setcookie("loginId", $_SESSION['loginId'], time() + 31536000, "/", ".example.com", 0, true);
}
#Authenticate User
function confirmUser($email_address, $password)
{
if (mysql_num_rows(mysql_query("select * from TABLE where email ='" . $email_address . "' and password =MD5('" . $password . "')"))) {
$loginstatus = 1; //account exists, login
return $loginstatus;
} else {
$loginstatus = 0; // Incorrect Credentials
return $loginstatus;
}
}
#Function to check login status
function checkLoggedin()
{
if (isset($_SESSION['email']) && isset($_SESSION['password']))
return true;
elseif (isset($_COOKIE['email']) && isset($_COOKIE['password'])) {
if (confirmUser($_COOKIE['email'], $_COOKIE['password'])) {
$sql_id = mysql_query("select id, email, password from TABLE where email ='" . $_COOKIE['email'] . "' and password ='" . $_COOKIE['password'] . "'");
$sql_array = mysql_fetch_row($sql_id);
#Register Session & Cookies Variables
$loginId = $sql_array[0];
$email = $sql_array[1];
$password = $sql_array[2];
//Clear all sessions and cookies first
clearsessionscookies();
createsessions($email, $password, $loginId);
return true;
} else {
clearsessionscookies();
return false;
}
} else
return false;
}
#Logout and clear session and cookies data
function clearsessionscookies()
{
unset($_SESSION['email']);
unset($_SESSION['password']);
unset($_SESSION['loginId']);
setcookie("PHPSESSID", null, time() - 31536000, "/", ".example.com", 0);
setcookie("email", null, time() - 31536000, "/", ".example.com", 0);
setcookie("password", null, time() - 31536000, "/", ".example.com", 0);
setcookie("loginId", null, time() - 31536000, "/", ".example.com", 0);
session_unset();
session_destroy();
ob_end_flush();
}
?>
createsessions() and clearsessionscookies() helps in handling session creation and deletion. Replace example.com to your cookie domain.
From your HTML Page, make an AJAX Call to a function and put the following code in it
<?php
#AJAX Call to this function
function checkLogin()
{
// Don't forget to protect against MySQL injection
$user_email = mysql_real_escape_string($_POST['emailaddress']);
$password = mysql_real_escape_string($_POST['password']);
if ($user_email == '' || $password == '') {
echo "Invalid email address or password. Please try again.";
$hasError = true;
}
$loginstatus = confirmUser($user_email, $password);
if ($loginstatus == 0) {
echo "Invalid ID or password. Please try again.";
$hasError = true;
} else // Login Now
{
if ($hasError != true) {
#Get User Details In Session
$sql_id = mysql_query("select loginId, email, password from TABLE where email ='" . $user_email . "' and password =MD5('" . $password . "')");
$sql_array = mysql_fetch_row($sql_id);
#Register Session & Cookies Variables
$loginId = $sql_array[0];
$email = $sql_array[1];
$password = $sql_array[2];
//Create Fresh sessions and cookies
createsessions($email, $password, $loginId);
//Redirect to Dashboard or wherever you want the customer to go post authentication
}
}
}
?>
I have purposely kept the LoginID so you can specifically handle the particular record for referencing with other tables you might have associated with the user table.
I hope this will work and help you keep the user logged in for the specified period in the cookie.
Your problem could be
session_regenerate_id(true);
This is going to delete your session and update it with a new session id and you are going to lose the association with any session data for the old session id.
From the docs for the parameter to this function.
delete_old_session
Whether to delete the old associated session file or not.
You said config.php calls session_start(); however I see you are using
ini_set(use.cookie, true)
That is wrong and causing your issue.

session variables disappear?

I am trying to learn more about password hashing and secure login, etc. and for that reason I am trying to duplicate this example here. I did not 100% duplicate this.
The problem I encounter is that when I enter my login credentials, the form goes to the process_login.php script which verifies the password and so forth, and sets the $_SESSION variables. Upon success, it is supposed to redirect to protected.php, a site which is only accessible when the user is logged in.
For me it does not work simply because the $_SESSION variables disappear!
I am at a point where the process_login.php script shows me that $_SESSION is set, and then I use header("Location: protected.php"); which then tells me the $_SESSION array is empty. How is this possible? I am missing the boat here...
Here are the parts of the code that are relevant:
process_login.php
process_login.php
include_once 'connect.php';
include_once 'functions.php';
sec_session_start();
if (isset($_POST['eml'], $_POST['h'])) {
$email = $_POST['eml'];
$pwd_hash = $_POST['h'];
if (login($email, $pwd_hash, $mysqli) == true) {
// in my situation, this returns true
// and the redirect to "protected.php" happens
header('Location: protected.php');
} else {
header("Location: error?err=Wrong password");
}
} else {
exit('Invalid Request');
}
The login() function
function login($email, $password, $mysqli) {
if ($stmt = $mysqli->prepare("SELECT id, email, pwd, salt FROM public WHERE email=? LIMIT 1")) {
$stmt->bind_param('s', $email);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($user_id, $email, $db_password, $salt);
$stmt->fetch();
$password = crypt($password, $salt);
if ($stmt->num_rows == 1) {
if (checkbrute($user_id, $mysqli) == true) {
// Account is locked
$status = "3";
$mysqli->query("INSERT INTO login_activity(user, status, ip)
VALUES ('$email', '$status', '{$_SERVER['REMOTE_ADDR']}')");
sleep(8);
header("Location: ../error?err=The account you try to access is currently blocked.");
return false;
} else {
// Check if the password in the database matches
// the password the user submitted.
if ($db_password == $password) {
// Password is correct!
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
// XSS protection as we might print this value
$user_id = preg_replace("/[^0-9]+/", "", $user_id);
$_SESSION['user_id'] = $user_id;
$_SESSION['login_string'] = hash('sha512', $password . $user_browser);
$status = "1";
$mysqli->query("INSERT INTO login_activity (user, status, ip)
VALUES ('{$_SESSION['user_id']}', '$status', '{$_SERVER['REMOTE_ADDR']}')");
return true;
} else {
// Password is not correct
// We record this attempt in the database
$status = "2";
$mysqli->query("INSERT INTO login_activity(user, status, ip)
VALUES ('$email', '$status', '{$_SERVER['REMOTE_ADDR']}')");
sleep(3);
header("Location: ../error?err=Password is not correct.");
return false;
}
}
} else {
// No user exists.
sleep(2);
header("Location: ../error?err=No user exists.");
return false;
}
header("Location: ../error?err=You can't see this.");
return false;
} else {
header("Location: ../error?err=DB fail: ".$mysqli->error);
return false;
}
}
protected.php
protected.php
<?php
include_once 'connect.php';
include_once 'functions.php';
sec_session_start();
// $return = login_check($mysqli);
print_r(get_defined_vars());
// this outputs an empty $_SESSION array
exit;
function sec_session_start()
function sec_session_start() {
$session_name = 'sec_session_id';
$secure = true;
$httponly = true;
if (ini_set('session.use_only_cookies', 1) === FALSE) {
header("Location: ../error?err=Could not initiate a safe session (ini_set)");
exit();
}
$cookieParams = session_get_cookie_params();
session_set_cookie_params($cookieParams["lifetime"],
$cookieParams["path"],
$cookieParams["domain"],
$secure,
$httponly);
session_name($session_name);
session_start();
session_regenerate_id();
}
I tried to see what happens if I just start the session using sec_session_start() and the following is the result:
include_once 'connect.php';
include_once 'functions.php';
sec_session_start();
$_SESSION["test"] = "works!";
header('Location: protected.php');
The output from print_r(get_defined_vars()); in protected.php is:
[_SESSION] => Array ( )
This may be of help in many session issues. I have used PHP for many years and love it,
but it is quirky!
In this code -
page1.php
<?phpsession_start();?>
<?php
$_SESSION['roman']="kitty";
echo (' Go To Page 2');
?>
page2.php
<?php session_start();?>
<?php
echo $_SESSION['roman'];
?>
Notice NO space in 'phpsession_start()' on page1,
but I did use a space in 'php session_start()' on page2.
Using NO space in page2, and the session variables are gone. With a space it works fine.
In page1, it does not matter, works either way with and without space. I have OTHER scripts where
it is the reverse. Will only work WITHOUT the space! So is something to try and check.
Now many may paste this code and have it work, or not work as I have described, but that is why
it is quirky!

PHP page shows up blank

I'm new to PHP and I'm following this tutorial on creating a log in page. When I finished the tutortial, the page is completely white, view sources shows a blank page as well. This is the error I'm getting "Parse error occurred
Message: syntax error, unexpected end of file" this is the code:
<?php
include_once 'psl-config.php';
function sec_session_start() {
$session_name = 'sec_session_id'; // Set a custom session name
$secure = SECURE;
// This stops JavaScript being able to access the session id.
$httponly = true;
// Forces sessions to only use cookies.
if (ini_set('session.use_only_cookies', 1) === FALSE) {
header("Location: ../error.php?err=Could not initiate a safe session (ini_set)");
exit();
}
// Gets current cookies params.
$cookieParams = session_get_cookie_params();
session_set_cookie_params($cookieParams["lifetime"],
$cookieParams["path"],
$cookieParams["domain"],
$secure,
$httponly);
// Sets the session name to the one set above.
session_name($session_name);
session_start(); // Start the PHP session
session_regenerate_id(); // regenerated the session, delete the old one.
function login($email, $password, $mysqli) {
// Using prepared statements means that SQL injection is not possible.
if ($stmt = $mysqli->prepare("SELECT id, username, password, salt
FROM members
WHERE email = ?
LIMIT 1")) {
$stmt->bind_param('s', $email); // Bind "$email" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
// get variables from result.
$stmt->bind_result($user_id, $username, $db_password, $salt);
$stmt->fetch();
// hash the password with the unique salt.
$password = hash('sha512', $password . $salt);
if ($stmt->num_rows == 1) {
// If the user exists we check if the account is locked
// from too many login attempts
if (checkbrute($user_id, $mysqli) == true) {
// Account is locked
// Send an email to user saying their account is locked
return false;
} else {
// Check if the password in the database matches
// the password the user submitted.
if ($db_password == $password) {
// Password is correct!
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
// XSS protection as we might print this value
$user_id = preg_replace("/[^0-9]+/", "", $user_id);
$_SESSION['user_id'] = $user_id;
// XSS protection as we might print this value
$username = preg_replace("/[^a-zA-Z0-9_\-]+/",
"",
$username);
$_SESSION['username'] = $username;
$_SESSION['login_string'] = hash('sha512',
$password . $user_browser);
// Login successful.
return true;
} else {
// Password is not correct
// We record this attempt in the database
$now = time();
$mysqli->query("INSERT INTO login_attempts(user_id, time)
VALUES ('$user_id', '$now')");
return false;
}
}
} else {
// No user exists.
return false;
}
}
}
function checkbrute($user_id, $mysqli) {
// Get timestamp of current time
$now = time();
// All login attempts are counted from the past 2 hours.
$valid_attempts = $now - (2 * 60 * 60);
if ($stmt = $mysqli->prepare("SELECT time
FROM login_attempts
WHERE user_id = ?
AND time > '$valid_attempts'")) {
$stmt->bind_param('i', $user_id);
// Execute the prepared query.
$stmt->execute();
$stmt->store_result();
// If there have been more than 5 failed logins
if ($stmt->num_rows > 5) {
return true;
} else {
return false;
}
}
}
function esc_url($url) {
if ('' == $url) {
return $url;
}
$url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%#$\|*\'()\\x80-\\xff]|i', '', $url);
$strip = array('%0d', '%0a', '%0D', '%0A');
$url = (string) $url;
$count = 1;
while ($count) {
$url = str_replace($strip, '', $url, $count);
}
$url = str_replace(';//', '://', $url);
$url = htmlentities($url);
$url = str_replace('&', '&', $url);
$url = str_replace("'", ''', $url);
if ($url[0] !== '/') {
// We're only interested in relative links from $_SERVER['PHP_SELF']
return '';
} else {
return $url;
}
}
?>
I'm guessing that your problem lies in these scripts...
include_once 'includes/db_connect.php';
include_once 'includes/functions.php';
As others have mentioned, you can enable error reporting in the php.ini but I believe it is enabled by default. What you'll need to do, is make sure you've got error checks for all your commands within those scripts.
EDIT: Removing last suggestion since sec_session_start(); is a function from your tutorial. It needs to be there.
(1) Put the following lines at the top of the script exactly below <?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
(2) Replace sec_session_start(); to session_start();
It should do the trick!
This is what I see when I look at your code:
<?php
include_once 'psl-config.php';
function sec_session_start() {
// lot of code here
// but closing tag of the function is missing
// therefore following functions are actually define inside sec_session_start()
// this should produce a parser error.
function login($email, $password, $mysqli) {
// lot of code here
}
function esc_url($url) {
// code
}
?>
So firstly, you should get the parser errors displayed by:
ini_set('display_errors', 1);
error_reporting(E_ALL);
Also, I don't see you actually call these functions anywhere. Defining functions does not put anything to the output (even if the functions themselves have echo/print statements).

"Dynamics" sessions

We have a login form that is processed by php and ajax. The ajax sends a request to the php page with the username and password to be logged in. It gets a response and if it's correct and working info, it logs them in:
The php page that takes requests has this code:
echo (checkLogin($_POST['user'], $_POST['pass']) ? 'true' : 'false');
if(checkLogin($_POST['user'], $_POST['pass']) == true)
logIn($_POST['user'], $_POST['pass']);
The functions used in that statement:
function logIn($user, $pass)
{
$_SESSION['sid'] = md5(md5($user) . md5($pass));
$_SESSION['username'] = $user;
$_SESSION['password'] = $pass;
}
function checkLogin($user, $pass)
{
$user = strtolower($user);
$pass = strtolower($pass);
$res = mysql_query("SELECT * FROM users WHERE username='".$user."'");
if(mysql_num_rows($res) == 1)
{
$data = mysql_fetch_assoc($res);
if($data['pass'] == aCrypt($pass))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
Now, it seems that the session is started and only able to be seen AFTER the user reloads the page. We need it to start the session right on the page...would we need to refresh the entire page with ajax? I don't really know where to go from here.
You probably want to use the Post-Redirect-Get pattern; after the user is successfully authenticated, use a redirect to send him to a new page.
As I noted above, please look into fixing the SQL injection and session fixation vulnerabilities in your code as well.

Categories