1

I'm trying to read in PHP a query string generated by an HTML form using <select multiple> or several <input type="checkbox"> with the same name, and I want to see all values associated with each name. An example of such a form:

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Form</title>
</head>
<body>
<h1>Form</h1>

<h2>Select</h2>
<form method="get" action="script.php">
<select multiple name="cantchange" size=5>
  <option value="a">Alpha</option>
  <option value="b">Bravo</option>
  <option value="c">Charlie</option>
  <option value="d">Delta</option>
  <option value="e">Echo</option>
</select><br>
Ctrl+click to choose multiple items, then
<input type="submit" name="op" value="Submit">
</form>

<h2>Checkbox set</h2>
<form method="get" action="script.php">
<label><input type="checkbox" name="cantchange" value="f"> Foxtrot</label><br>
<label><input type="checkbox" name="cantchange" value="g"> Golf</label><br>
<label><input type="checkbox" name="cantchange" value="h"> Hotel</label><br>
<label><input type="checkbox" name="cantchange" value="i"> India</label><br>
<label><input type="checkbox" name="cantchange" value="j"> Juliett</label><br>
<input type="submit" name="op" value="Submit">
</form>

</body>
</html>

This produces a query string of the following form:

cantchange=a&cantchange=b&cantchange=c&op=Submit

But when PHP parses this query string into $_GET, it sees only the last value with each name.

Answers to "How to get multiple selected values of select box in php?" and its duplicates recommend appending [] to the name attribute of each select or input element in the form to make PHP collect values into an array. But this assumes that the author of the script that interprets the form can change the form. This isn't always possible. And even when I can change the form, adding the brackets reveals that the application is written in PHP, tempting intruders to try PHP-specific exploits against the server first.

Community
  • 1
  • 1
Damian Yerrick
  • 4,602
  • 2
  • 26
  • 64

1 Answers1

0

If you can't change the form, you'll have to do more of the work of parsing the query strings yourself. First split the query string at the &, split each name-value pair at the =, and unescape the name and value. The following script parses forms like this using functions that reimplement much of Python's query string parser API.

<?php
// Parse query string from <select multiple> without changing the
// form to add brackets to the element's name attribute.
// Implements a subset of Python's urllib.parse.parse_* APIs
// <https://docs.python.org/3/library/urllib.parse.html>
// Copyright 2017 Damian Yerrick
// License: WTFPL v2 <http://www.wtfpl.net/txt/copying/>

/**
 * Parses an application/x-www-form-urlencoded string, such as the
 * output of http_build_query(), into an array of 2-arrays.
 * @param $qs the query string to parse
 * @param $keep_blank_values if false, omit pairs whose values are ''
 * @return data in the form [[key, value], ...]
 */
function parse_qsl($qs, $keep_blank_values=false) {
  $qs = explode("&", $qs);
  $out = [];
  foreach ($qs as $pair) {
    $pair = explode("=", $pair, 2);
    if (count($pair) != 2) continue;
    $value = urldecode($pair[1]);
    if ($keep_blank_values || $value !== '') {
      $key = urldecode($pair[0]);
      $out[] = [$key, $value];
    }
  }
  return $out;
}

/**
 * Parses an application/x-www-form-urlencoded string into an
 * associative array from keys to arrays of values.
 * @param $qs the query string to parse
 * @param $keep_blank_values if false, omit pairs whose values are ''
 * @param data in the form [key=>[value, ...], ...]
 */
function parse_qs($qs, $keep_blank_values=false) {
  $data = parse_qsl($qs, $keep_blank_values);
  $out = [];
  foreach ($data as $pair) {
    list($key, $value) = $pair;
    if (!array_key_exists($key, $out)) {
      $out[$key] = [];
    }
    $out[$key][] = $value;
  }
  return $out;
}

function hrepr($value) {
  return htmlspecialchars(var_export($value, true));
}

?><!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Form</title>
</head>
<body>
<h1>Form</h1>
<pre>
$_GET is <?=hrepr($_GET)?>

Query string is <?=hrepr($_SERVER['QUERY_STRING'])?>

parse_qsl is <?=hrepr(parse_qsl($_SERVER['QUERY_STRING']))?>

parse_qs is <?=hrepr(parse_qs($_SERVER['QUERY_STRING']))?>
</pre>
</body>
</html>
Damian Yerrick
  • 4,602
  • 2
  • 26
  • 64