public function testQueryStringListParsing()
 {
     $map = array('' => array(), '&' => array(), '=' => array(array('', '')), '=&' => array(array('', '')), 'a=b' => array(array('a', 'b')), 'a[]=b' => array(array('a[]', 'b')), 'a=' => array(array('a', '')), '. [=1' => array(array('. [', '1')), 'a=b&c=d' => array(array('a', 'b'), array('c', 'd')), 'a=b&a=c' => array(array('a', 'b'), array('a', 'c')), '&a=b&' => array(array('a', 'b')), '=a' => array(array('', 'a')), '&&&' => array(), 'a%20b=c%20d' => array(array('a b', 'c d')));
     $parser = new PhutilQueryStringParser();
     foreach ($map as $query_string => $expected) {
         $this->assertEqual($expected, $parser->parseQueryStringToPairList($query_string));
     }
 }
示例#2
0
 /**
  * Produces a value safe to pass to `CURLOPT_POSTFIELDS`.
  *
  * @return wild   Some value, suitable for use in `CURLOPT_POSTFIELDS`.
  */
 private function formatRequestDataForCURL()
 {
     // We're generating a value to hand to cURL as CURLOPT_POSTFIELDS. The way
     // cURL handles this value has some tricky caveats.
     // First, we can return either an array or a query string. If we return
     // an array, we get a "multipart/form-data" request. If we return a
     // query string, we get an "application/x-www-form-urlencoded" request.
     // Second, if we return an array we can't duplicate keys. The user might
     // want to send the same parameter multiple times.
     // Third, if we return an array and any of the values start with "@",
     // cURL includes arbitrary files off disk and sends them to an untrusted
     // remote server. For example, an array like:
     //
     //   array('name' => '@/usr/local/secret')
     //
     // ...will attempt to read that file off disk and transmit its contents with
     // the request. This behavior is pretty surprising, and it can easily
     // become a relatively severe security vulnerability which allows an
     // attacker to read any file the HTTP process has access to. Since this
     // feature is very dangerous and not particularly useful, we prevent its
     // use. Broadly, this means we must reject some requests because they
     // contain an "@" in an inconvenient place.
     // Generally, to avoid the "@" case and because most servers usually
     // expect "application/x-www-form-urlencoded" data, we try to return a
     // string unless there are files attached to this request.
     $data = $this->getData();
     $files = $this->files;
     $any_data = $data || is_string($data) && strlen($data);
     $any_files = (bool) $this->files;
     if (!$any_data && !$any_files) {
         // No files or data, so just bail.
         return null;
     }
     if (!$any_files) {
         // If we don't have any files, just encode the data as a query string,
         // make sure it's not including any files, and we're good to go.
         if (is_array($data)) {
             $data = http_build_query($data, '', '&');
         }
         $this->checkForDangerousCURLMagic($data, $is_query_string = true);
         return $data;
     }
     // If we've made it this far, we have some files, so we need to return
     // an array. First, convert the other data into an array if it isn't one
     // already.
     if (is_string($data)) {
         // NOTE: We explicitly don't want fancy array parsing here, so just
         // do a basic parse and then convert it into a dictionary ourselves.
         $parser = new PhutilQueryStringParser();
         $pairs = $parser->parseQueryStringToPairList($data);
         $map = array();
         foreach ($pairs as $pair) {
             list($key, $value) = $pair;
             if (array_key_exists($key, $map)) {
                 throw new Exception(pht('Request specifies two values for key "%s", but parameter ' . 'names must be unique if you are posting file data due to ' . 'limitations with cURL.', $key));
             }
             $map[$key] = $value;
         }
         $data = $map;
     }
     foreach ($data as $key => $value) {
         $this->checkForDangerousCURLMagic($value, $is_query_string = false);
     }
     foreach ($this->files as $name => $info) {
         if (array_key_exists($name, $data)) {
             throw new Exception(pht('Request specifies a file with key "%s", but that key is also ' . 'defined by normal request data. Due to limitations with cURL, ' . 'requests that post file data must use unique keys.', $name));
         }
         $tmp = new TempFile($info['name']);
         Filesystem::writeFile($tmp, $info['data']);
         $this->temporaryFiles[] = $tmp;
         // In 5.5.0 and later, we can use CURLFile. Prior to that, we have to
         // use this "@" stuff.
         if (class_exists('CURLFile', false)) {
             $file_value = new CURLFile((string) $tmp, $info['mime'], $info['name']);
         } else {
             $file_value = '@' . (string) $tmp;
         }
         $data[$name] = $file_value;
     }
     return $data;
 }