Project

General

Profile

Paste
Download (21.1 KB) Statistics
| Branch: | Revision:

root / .drush / drush_make / drush_make.utilities.inc @ 09c5c067

1
<?php
2
// $Id: drush_make.utilities.inc,v 1.1.2.49 2010/09/09 07:11:00 dmitrig01 Exp $
3

    
4
function drush_make_ensure_version() {
5
  // Check version 4:
6
  if (function_exists('drush_get_global_options')) {
7
    return '4.0';
8
  }
9
  // Check version 2.1 or up:
10
  $options = drush_get_option_help();
11
  if (isset($options['-n, --nocolor'])) {
12
    return '2.1';
13
  }
14

    
15
  if (isset($options['--nocolor'])) {
16
    return '2.2';
17
  }
18

    
19
  drush_set_error(dt('Drush version 2.1 or higher is required for drush make to work.'));
20
  return;
21
}
22

    
23
/**
24
 * Parse Drupal info file format.
25
 *
26
 * Copied with modifications from includes/common.inc.
27
 *
28
 * @see drupal_parse_info_file
29
 */
30
function drush_make_parse_info_file($makefile, $parsed = TRUE) {
31
  if (!($data = drush_make_get_data($makefile))) {
32
    return FALSE;
33
  }
34
  if (!($info = _drush_make_parse_info_file($data))) {
35
    return FALSE;
36
  }
37
  if (!empty($info['includes'])) {
38
    $include_path = dirname($makefile);
39
    $includes = array();
40
    if (!empty($info['includes']) && is_array($info['includes'])) {
41
      foreach ($info['includes'] as $include) {
42
        if (is_string($include)) {
43
          if (drush_make_valid_url($include, TRUE) && ($file = drush_make_parse_info_file($include, FALSE))) {
44
            $includes[] = $file;
45
          }
46
          else if (file_exists($include_path .'/'. $include) && ($file = drush_make_parse_info_file($include_path .'/'. $include, FALSE))) {
47
            $includes[] = $file;
48
          }
49
          else {
50
            drush_make_error('BUILD_ERROR', dt("Include file missing: %include", array('%include' => $include)));
51
          }
52
        }
53
      }
54
    }
55
    $includes[] = $data;
56
    $data = implode("\n", $includes);
57
    $info = _drush_make_parse_info_file($data);
58
  }
59
  if ($parsed) {
60
    return $info;
61
  }
62
  else {
63
    return $data;
64
  }
65
}
66

    
67
function _drush_make_parse_info_file($data) {
68
  if (!$data) {
69
    return FALSE;
70
  }
71

    
72
  if (preg_match_all('
73
    @^\s*                           # Start at the beginning of a line, ignoring leading whitespace
74
    ((?:
75
      [^=;\[\]]|                    # Key names cannot contain equal signs, semi-colons or square brackets,
76
      \[[^\[\]]*\]                  # unless they are balanced and not nested
77
    )+?)
78
    \s*=\s*                         # Key/value pairs are separated by equal signs (ignoring white-space)
79
    (?:
80
      ("(?:[^"]|(?<=\\\\)")*")|     # Double-quoted string, which may contain slash-escaped quotes/slashes
81
      (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
82
      ([^\r\n]*?)                   # Non-quoted string
83
    )\s*$                           # Stop at the next end of a line, ignoring trailing whitespace
84
    @msx', $data, $matches, PREG_SET_ORDER)) {
85
    $info = array();
86
    foreach ($matches as $match) {
87
      // Fetch the key and value string
88
      $i = 0;
89
      foreach (array('key', 'value1', 'value2', 'value3') as $var) {
90
        $$var = isset($match[++$i]) ? $match[$i] : '';
91
      }
92
      $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;
93

    
94
      // Parse array syntax
95
      $keys = preg_split('/\]?\[/', rtrim($key, ']'));
96
      $last = array_pop($keys);
97
      $parent = &$info;
98

    
99
      // Create nested arrays
100
      foreach ($keys as $key) {
101
        if ($key == '') {
102
          $key = count($parent);
103
        }
104
        if (!isset($parent[$key]) || !is_array($parent[$key])) {
105
          $parent[$key] = array();
106
        }
107
        $parent = &$parent[$key];
108
      }
109

    
110
      // Handle PHP constants
111
      if (defined($value)) {
112
        $value = constant($value);
113
      }
114

    
115
      // Insert actual value
116
      if ($last == '') {
117
        $last = count($parent);
118
      }
119
      $parent[$last] = $value;
120
    }
121
    return $info;
122
  }
123
  return FALSE;
124
}
125

    
126
function drush_make_validate_info_file($info) {
127
  // Assume no errors to start.
128
  $errors = FALSE;
129

    
130
  if (empty($info['core'])) {
131
    drush_make_error('BUILD_ERROR', dt("The 'core' attribute is required"));
132
    $errors = TRUE;
133
  }
134
  // Standardize on core.
135
  elseif (preg_match('/^(\d+)(\.(x|(\d+)(-[a-z0-9]+)?))?$/', $info['core'], $matches)) {
136
    // An exact version of core has been specified, so pass that to an
137
    // internal variable for storage.
138
    if (isset($matches[4])) {
139
      $info['core_release'] = $info['core'];
140
    }
141
    // Format the core attribute consistently.
142
    $info['core'] = $matches[1] . '.x';
143
  }
144
  else {
145
    drush_make_error('BUILD_ERROR', dt("The 'core' attribute %core has an incorrect format.", array('%core' => $info['core'])));
146
    $errors = TRUE;
147
  }
148

    
149
  if (!isset($info['api'])) {
150
    $info['api'] = 2;
151
    drush_log(dt("You need to specify an API version of two in your makefile:\napi = 2"), 'warning');
152
  }
153
  elseif ($info['api'] != 2) {
154
    drush_make_error('BUILD_ERROR', dt("The specified API attribute is incompatible with this version of Drush Make."));
155
    $errors = TRUE;
156
  }
157

    
158
  $names = array();
159

    
160
  // Process projects.
161
  if (isset($info['projects'])) {
162
    if (!is_array($info['projects'])) {
163
      drush_make_error('BUILD_ERROR', dt("'projects' attribute must be an array."));
164
      $errors = TRUE;
165
    }
166
    else {
167
      foreach ($info['projects'] as $project => $project_data) {
168
        // Project has an attributes array.
169
        if (is_string($project) && is_array($project_data)) {
170
          if (in_array($project, $names)) {
171
            drush_make_error('BUILD_ERROR', dt("Project %project defined twice (remove the first projects[] = %project).", array('%project' => $project)));
172
            $errors = TRUE;
173
          }
174
          $names[] = $project;
175
          foreach ($project_data as $attribute => $value) {
176
            // Unset disallowed attributes.
177
            if (in_array($attribute, array('contrib_destination'))) {
178
              unset($info['projects'][$project][$attribute]);
179
            }
180
            // Prevent malicious attempts to access other areas of the filesystem.
181
            elseif (in_array($attribute, array('subdir', 'directory_name')) && !drush_make_safe_path($value)) {
182
              drush_make_error('BUILD_ERROR', dt("Illegal path %path for '%attribute' attribute in project %project.", array('%path' => $value, '%attribute' => $attribute, '%project' => $project)));
183
              $errors = TRUE;
184
            }
185
          }
186
        }
187
        // Cover if there is no project info, it's just a project name.
188
        elseif (is_numeric($project) && is_string($project_data)) {
189
          if (in_array($project_data, $names)) {
190
            drush_make_error('BUILD_ERROR', dt("Project %project defined twice (remove the first projects[] = %project).", array('%project' => $project_data)));
191
            $errors = TRUE;
192
          }
193
          $names[] = $project_data;
194
          unset($info['projects'][$project]);
195
          $info['projects'][$project_data] = array();
196
        }
197
        // Convert shorthand project version style to array format.
198
        elseif (is_string($project_data)) {
199
          if (in_array($project, $names)) {
200
            drush_make_error('BUILD_ERROR', dt("Project %project defined twice (remove the first projects[] = %project).", array('%project' => $project)));
201
            $errors = TRUE;
202
          }
203
          $names[] = $project;
204
          $info['projects'][$project] = array('version' => $project_data);
205
        }
206
        else {
207
          drush_make_error('BUILD_ERROR', dt('Project %project incorrectly specified.', array('%project' => $project)));
208
          $errors = TRUE;
209
        }
210
      }
211
    }
212
  }
213
  if (isset($info['libraries'])) {
214
    if (!is_array($info['libraries'])) {
215
      drush_make_error('BUILD_ERROR', dt("'libraries' attribute must be an array."));
216
      $errors = TRUE;
217
    }
218
    else {
219
      foreach($info['libraries'] as $library => $library_data) {
220
        if (is_array($library_data)) {
221
          foreach($library_data as $attribute => $value) {
222
            // Unset disallowed attributes.
223
            if (in_array($attribute, array('contrib_destination'))) {
224
              unset($info['libraries'][$library][$attribute]);
225
            }
226
            // Prevent malicious attempts to access other areas of the filesystem.
227
            elseif (in_array($attribute, array('contrib-destination', 'directory_name')) && !drush_make_safe_path($value)) {
228
              drush_make_error('BUILD_ERROR', dt("Illegal path %path for '%attribute' attribute in library %library.", array('%path' => $value, '%attribute' => $attribute, '%library' => $library)));
229
              $errors = TRUE;
230
            }
231
          }
232
        }
233
      }
234
    }
235
  }
236

    
237
  foreach (drush_command_implements('drush_make_validate_info') as $module) {
238
    $function = $module .'_drush_make_validate_info';
239
    $return = $function($info);
240
    if ($return) {
241
      $info = $return;
242
    }
243
    else {
244
      $errors = TRUE;
245
    }
246
  }
247

    
248
  if ($errors) {
249
    return FALSE;
250
  }
251
  return $info;
252
}
253

    
254
/**
255
 * Verify the syntax of the given URL.
256
 *
257
 * Copied verbatim from includes/common.inc
258
 *
259
 * @see valid_url
260
 */
261
function drush_make_valid_url($url, $absolute = FALSE) {
262
  if ($absolute) {
263
    return (bool)preg_match("
264
      /^                                                      # Start at the beginning of the text
265
      (?:ftp|https?):\/\/                                     # Look for ftp, http, or https schemes
266
      (?:                                                     # Userinfo (optional) which is typically
267
        (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)*      # a username or a username and password
268
        (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@          # combination
269
      )?
270
      (?:
271
        (?:[a-z0-9\-\.]|%[0-9a-f]{2})+                        # A domain name or a IPv4 address
272
        |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\])         # or a well formed IPv6 address
273
      )
274
      (?::[0-9]+)?                                            # Server port number (optional)
275
      (?:[\/|\?]
276
        (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})   # The path and query (optional)
277
      *)?
278
    $/xi", $url);
279
  }
280
  else {
281
    return (bool)preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
282
  }
283
}
284

    
285
function drush_make_tmp($set = TRUE) {
286
  static $tmp_dir;
287
  if (!isset($tmp_dir) && $set) {
288
    $tmp_dir = sys_get_temp_dir();
289
    if (strrpos($tmp_dir, '/') == strlen($tmp_dir) - 1) {
290
      $tmp_dir .= 'drush_make_tmp_' . time();
291
    }
292
    else {
293
      $tmp_dir .= '/drush_make_tmp_' . time();
294
    }
295
    drush_make_mkdir($tmp_dir);
296
  }
297
  return $tmp_dir;
298
}
299

    
300
function drush_make_clean_tmp() {
301
  if (!($tmp_dir = drush_make_tmp(FALSE))) {
302
    return;
303
  }
304
  if (!drush_get_option('no-clean', FALSE)) {
305
    drush_shell_exec('rm -rf %s', $tmp_dir);
306
  }
307
  else {
308
    drush_log(dt('Temporary directory: %dir', array('%dir' => $tmp_dir)), 'ok');
309
  }
310
}
311

    
312
function drush_make_prepare_install($build_path) {
313
  $default = drush_make_tmp() . '/__build__/sites/default';
314
  drush_shell_exec("cp %s %s", $default . '/default.settings.php', $default . '/settings.php');
315
  drush_make_mkdir($default . '/files');
316
  drush_shell_exec("chmod a+w %s %s", $default . '/settings.php', $default . '/files');
317
}
318

    
319
function drush_make_md5() {
320
  if (drush_shell_exec("( find %s -type f -exec cksum {} \; )", drush_make_tmp())) {
321
    $hashes = array();
322
    foreach (drush_shell_exec_output() as $line) {
323
      // Remove the temporary build path which includes a (relatively)
324
      // unique timestamp.
325
      $line = str_replace(drush_make_tmp(), '', $line);
326
      // Trim to sanitize.
327
      $line = trim($line);
328
      $hashes[] = $line;
329
    }
330
    sort($hashes);
331
    $output = implode("\n", $hashes);
332
    drush_log(dt('Build hash: %md5', array('%md5' => md5($output))), 'ok');
333
  }
334
}
335

    
336
function drush_make_tar($build_path) {
337
  $tmp_path = drush_make_tmp();
338

    
339
  drush_make_mkdir(dirname($build_path));
340
  $filename = basename($build_path);
341
  $dirname = basename($build_path, '.tar.gz');
342
  // Move the build directory to a more human-friendly name, so that tar will
343
  // use it instead.
344
  drush_shell_exec("mv %s %s", $tmp_path . '/__build__', $tmp_path . '/' . $dirname);
345
  // Only move the tar file to it's final location if it's been built
346
  // successfully.
347
  if (drush_shell_exec("tar -C %s -Pczf %s %s", $tmp_path, $tmp_path . '/' . $filename, $dirname)) {
348
    drush_shell_exec("mv %s %s", $tmp_path . '/' . $filename, $build_path);
349
  };
350
  // Move the build directory back to it's original location for consistency.
351
  drush_shell_exec("mv %s %s", $tmp_path . '/' . $dirname, $tmp_path . '/__build__');
352
}
353

    
354
/**
355
 * Logs an error unless the --force-complete command line option is specified.
356
 */
357
function drush_make_error($error_code, $message) {
358
  if (drush_get_option('force-complete')) {
359
    drush_log("$error_code: $message -- build forced", 'warning');
360
  }
361
  else {
362
    drush_set_error($error_code, $message);
363
  }
364
}
365

    
366
/**
367
 * Checks an attribute's path to ensure it's not maliciously crafted.
368
 *
369
 * @param $path
370
 *   The path to check.
371
 */
372
function drush_make_safe_path($path) {
373
  return !preg_match("+^/|^\.\.|/\.\./+", $path);
374
}
375

    
376
if (!function_exists('drush_shell_cd_and_exec')) {
377
  function drush_shell_cd_and_exec($effective_wd, $cmd) {
378
    $args = func_get_args();
379

    
380
    $effective_wd = array_shift($args);
381
    $cwd = getcwd();
382
    drush_op('chdir', $effective_wd);
383
    $result = call_user_func_array('drush_shell_exec', $args);
384
    drush_op('chdir', $cwd);
385
    return $result;
386
  }
387
}
388

    
389
/**
390
 * Get data based on the source.
391
 *
392
 * This is a helper function to abstract the retrieval of data, so that it can
393
 * come from files, STDIN, etc.  Currently supports filepath and STDIN.
394
 *
395
 * @param $data_source
396
 *   The path to a file, or '-' for STDIN.
397
 * @return
398
 *   The raw data as a string.
399
 */
400
function drush_make_get_data($data_source) {
401
  if ($data_source == '-') {
402
    // See http://drupal.org/node/499758 before changing this.
403
    $stdin = fopen("php://stdin","r");
404
    $data = '';
405
    $has_input = FALSE;
406
  
407
    while ($line = fgets($stdin)) {
408
      $has_input = TRUE;
409
      $data .= $line;
410
    }
411
  
412
    if ($has_input) {
413
      return $data;
414
    }
415
    return FALSE;
416
  }
417
  else {
418
    $data = file_get_contents($data_source);
419
  }
420
  return $data;
421
}
422

    
423
/**
424
 * Helper to provide sys_get_temp_dir if on php < 5.2.1
425
 */
426
if (!function_exists('sys_get_temp_dir')) {
427
  // Based on http://www.phpit.net/
428
  // article/creating-zip-tar-archives-dynamically-php/2/
429
  function sys_get_temp_dir() {
430
    // Try to get from environment variable
431
    if (!empty($_ENV['TMP'])){
432
      return realpath($_ENV['TMP']);
433
    }
434
    else if (!empty($_ENV['TMPDIR'])){
435
      return realpath($_ENV['TMPDIR']);
436
    }
437
    else if (!empty($_ENV['TEMP'])){
438
      return realpath($_ENV['TEMP']);
439
    }
440
    else {
441
      // Detect by creating a temporary file
442
      // Try to use system's temporary directory
443
      // as random name shouldn't exist
444
      $temp_file = tempnam(md5(uniqid(rand(), TRUE)), '');
445
      if ($temp_file) {
446
        $temp_dir = realpath(dirname($temp_file));
447
        unlink($temp_file);
448
        return $temp_dir;
449
      }
450
      else {
451
        return FALSE;
452
      }
453
    }
454
  }
455
}
456

    
457
/**
458
 * Drush make parallel to drupal_http_request, but writes responses to a file.
459
 */
460
function drush_make_http_request($url, $destination, $headers = array(), $method = 'GET', $data = NULL, $retry = 5) {
461
  global $db_prefix;
462

    
463
  $result = new stdClass();
464

    
465
  // Parse the URL and make sure we can handle the schema.
466
  $uri = parse_url($url);
467

    
468
  if ($uri == FALSE) {
469
    $result->error = 'unable to parse URL';
470
    $result->code = -1001;
471
    return $result;
472
  }
473

    
474
  if (!isset($uri['scheme'])) {
475
    $result->error = 'missing schema';
476
    $result->code = -1002;
477
    return $result;
478
  }
479

    
480
  switch ($uri['scheme']) {
481
    case 'http':
482
    case 'feed':
483
      $port = isset($uri['port']) ? $uri['port'] : 80;
484
      $host = $uri['host'] . ($port != 80 ? ':'. $port : '');
485
      $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15);
486
      break;
487
    case 'https':
488
      // Note: Only works for PHP 4.3 compiled with OpenSSL.
489
      $port = isset($uri['port']) ? $uri['port'] : 443;
490
      $host = $uri['host'] . ($port != 443 ? ':'. $port : '');
491
      $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, 20);
492
      break;
493
    default:
494
      $result->error = 'invalid schema '. $uri['scheme'];
495
      $result->code = -1003;
496
      return $result;
497
  }
498

    
499
  // Make sure the socket opened properly.
500
  if (!$fp) {
501
    // When a network error occurs, we use a negative number so it does not
502
    // clash with the HTTP status codes.
503
    $result->code = -$errno;
504
    $result->error = trim($errstr);
505

    
506
    return $result;
507
  }
508

    
509
  // Construct the path to act on.
510
  $path = isset($uri['path']) ? $uri['path'] : '/';
511
  if (isset($uri['query'])) {
512
    $path .= '?'. $uri['query'];
513
  }
514

    
515
  // Create HTTP request.
516
  $defaults = array(
517
    // RFC 2616: "non-standard ports MUST, default ports MAY be included".
518
    // We don't add the port to prevent from breaking rewrite rules checking the
519
    // host that do not take into account the port number.
520
    'Host' => "Host: $host",
521
    'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
522
  );
523

    
524
  // Only add Content-Length if we actually have any content or if it is a POST
525
  // or PUT request. Some non-standard servers get confused by Content-Length in
526
  // at least HEAD/GET requests, and Squid always requires Content-Length in
527
  // POST/PUT requests.
528
  $content_length = strlen($data);
529
  if ($content_length > 0 || $method == 'POST' || $method == 'PUT') {
530
    $defaults['Content-Length'] = 'Content-Length: '. $content_length;
531
  }
532

    
533
  // If the server url has a user then attempt to use basic authentication
534
  if (isset($uri['user'])) {
535
    $defaults['Authorization'] = 'Authorization: Basic '. base64_encode($uri['user'] . (!empty($uri['pass']) ? ":". $uri['pass'] : ''));
536
  }
537

    
538
  // If the database prefix is being used by SimpleTest to run the tests in a copied
539
  // database then set the user-agent header to the database prefix so that any
540
  // calls to other Drupal pages will run the SimpleTest prefixed database. The
541
  // user-agent is used to ensure that multiple testing sessions running at the
542
  // same time won't interfere with each other as they would if the database
543
  // prefix were stored statically in a file or database variable.
544
  if (is_string($db_prefix) && preg_match("/^simpletest\d+$/", $db_prefix, $matches)) {
545
    $defaults['User-Agent'] = 'User-Agent: ' . $matches[0];
546
  }
547

    
548
  foreach ($headers as $header => $value) {
549
    $defaults[$header] = $header .': '. $value;
550
  }
551

    
552
  $request = $method .' '. $path ." HTTP/1.0\r\n";
553
  $request .= implode("\r\n", $defaults);
554
  $request .= "\r\n\r\n";
555
  $request .= $data;
556

    
557
  $result->request = $request;
558

    
559
  fwrite($fp, $request);
560

    
561
  $fp_dest = fopen($destination, 'w');
562
  $all_headers = FALSE;
563

    
564
  // Fetch response.
565
  $split = '';
566
  while (!feof($fp) && $chunk = fread($fp, 1024)) {
567
    if (strpos($chunk, "\r\n\r\n") !== FALSE && !$all_headers) {
568
      $all_headers = TRUE;
569
      list($header, $body) = explode("\r\n\r\n", $chunk, 2);
570
      $split .= $header;
571
      fwrite($fp_dest, $body);
572
    }
573
    elseif (!$all_headers) {
574
      $split .= $chunk;
575
    }
576
    else {
577
      fwrite($fp_dest, $chunk);
578
    }
579
  }
580
  fclose($fp_dest);
581
  fclose($fp);
582

    
583
  // Parse response.
584
  $split = preg_split("/\r\n|\n|\r/", $split);
585

    
586
  list($protocol, $code, $status_message) = explode(' ', trim(array_shift($split)), 3);
587
  $result->protocol = $protocol;
588
  $result->status_message = $status_message;
589

    
590
  $result->headers = array();
591

    
592
  // Parse headers.
593
  while ($line = trim(array_shift($split))) {
594
    list($header, $value) = explode(':', $line, 2);
595
    if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
596
      // RFC 2109: the Set-Cookie response header comprises the token Set-
597
      // Cookie:, followed by a comma-separated list of one or more cookies.
598
      $result->headers[$header] .= ','. trim($value);
599
    }
600
    else {
601
      $result->headers[$header] = trim($value);
602
    }
603
  }
604

    
605
  $responses = array(
606
    100 => 'Continue', 101 => 'Switching Protocols',
607
    200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
608
    300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
609
    400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed',
610
    500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported'
611
  );
612
  // RFC 2616 states that all unknown HTTP codes must be treated the same as the
613
  // base code in their class.
614
  if (!isset($responses[$code])) {
615
    $code = floor($code / 100) * 100;
616
  }
617

    
618
  $result->code = $code;
619
  return $result;
620
}
621

    
622
/**
623
 * Cross-platform compatible helper function to recursively create a directory tree.
624
 * @see http://theserverpages.com/php/manual/en/function.mkdir.php#50383
625
 */
626
function drush_make_mkdir($path) {
627
  return is_dir($path) || (drush_make_mkdir(dirname($path)) && drush_shell_exec('mkdir %s', $path));
628
}