Always Validate Data

The following posts are coming up!

Recent Posts



In the making of my REST API plugin’s extension, I had written out a way to select what happened on a specific date. While adding that kind of code into a shortcode was something I’d done before, I struggled to try and understand how I could do that with a widget in a way that was safe.

On Data Validation

The main function that does all the real work expects a date parameter in the format of MM-DD which it converts to YYYY-MM-DD so I can run date() calls on it later on. The reason that works is that the PHP date() function can parse the YYYY-MM-DD format on it’s own. To that end, I have to write the widget and shortcode in such a way that it’s impossible to pass on bad data.

This is a matter of an age old problem: sanitize, escape, and validate.

If you’ve written a plugin and think that sounds familiar, yes, it is a common sticking point of mine. You must sanitize your data before you save it. You must escape it before you display it. You should validate it to make sure you’re not letting people make silly mistakes.

The ‘problem’ is that there are a lot of ways to do that, and the ultimate question is this “What is the data I’m trying to save supposed to look like?” If it’s a URL, you sanitize for a URL. If it’s a number, you sanitize for a number. That’s the easy part. It’s just really time consuming and difficult. Still. Sanitize early, escape late, and always validate.

The Main Function Sanitizes and Validates

Instead of only having the shortcode or the widget make sure the date was right, I decided to attack the matter further up the chain. Or down the chain, depending on your point of view. Both the shortcode and widget call the same master function so for that, I make sure that the value of $this_day is never anything than what I want:

	public static function on_this_day( $this_day = 'today' ) {
		$this_day = sanitize_text_field( $this_day );
		if ( $this_day !== 'today' ) {
			$month = substr( $this_day, 0, 2);
			$day = substr( $this_day, 3, 2);
			$this_day = ( checkdate ( $month, $day , date('Y') ) == true )? $this_day : 'today' ;	
		}

	[... the rest of the code continues on]
	}

By using checkdate(), I verify that the value is either a valid date or I force it to be ‘today’. This prevents someone from passing bad data in the shortcode. I sanitize it as well as validate, using sanitize_text_field() because there’s no reason to even allow non-safe data anywhere further in my code.

The Shortcode Validates and Sanitizes

The way a shortcode works in WordPress is you can pass attributes to it and the shortcode parses that for you. My shortcode is pretty basic: [on-this-day date="MM-DD"] But since a shortcode lives in the world of ‘users can enter whatever they want’, I have to play Captain Beaver Dam and stop them from using [on-this-day date="Elvis"] for example.

I do this by sanitizing the attribute and then validating it’s the format I think it is:

	public function on_this_day_shortcode( $atts = [] ) {
		$attributes = shortcode_atts([
			'date' => 'today',
		], $atts);

		$this_day = sanitize_text_field($attributes['date']);
		if ( $this_day !== 'today' ) {
			$month = substr( $this_day, 0, 2);
			$day = substr( $this_day, 3, 2);
			$this_day = ( checkdate ( $month, $day , date('Y') ) == true )? $this_day : 'today' ;
		}
		$onthisday = $this->on_this_day( $this_day );
		return $onthisday;
	}

If you’re thinking that looks the same as what I do in the main widget, you’re right! I do it here to make sure I’m never sending bad data to the function. I do it in the function since I’m not actually psychic and people have done some pretty weird things. Also they may decide to extend my code one day and write something that calls the function.

The Widget Validates and Sanitizes

At this point, you’re expecting this, right? It’s actually more important that the widget do this than anything else. The code on my server won’t accept incorrectly formatted data, so the worst that happens is you get a bad data reply. The reason all this matters more for the widget though is that the widget stores data in the database.

If you’re saving data and you’re not making it safe, you’re putting yourself at risk. Repeat that until it’s second nature.

In my widget, there’s an update function that saves the data to the database.

	function update( $new_instance, $old_instance ) {
		$new_instance['title'] = strip_tags( $new_instance['title'] );

		$new_instance['date'] = substr( $new_instance['date'], 0, 5);
		$month = substr( $new_instance['date'], 0, 2);
		$day = substr( $new_instance['date'], 3, 2);
		if ( checkdate( $month, $day, date("Y") ) == false ) $new_instance['date'] = '';
		$new_instance['date']  = strip_tags( $new_instance['date'] );

		return $new_instance;
	}

You’ll notice that, yet again, I use checkdate() to make sure the date is a valid value. But I’m also using strip_tags() to remove any HTML. You could use wp_strip_all_tags() which is even more hardcore, but in this moment, they both work.

The Data Is Cleaned Three Ways

In a way, it’s overkill. I don’t need to validate the data in the shortcode really, because I’m doing that in the main function. I do need to validate in the widget, since I want to make sure I’m not saving bad data, and I don’t want people to see invalid data on the widget settings page. At the same time, by making sure everything is as sane and secure and valid, I’m limiting possible vulnerabilities down the road.


Posted

in

by

%d bloggers like this: