Hi,

I'm developing the ipaper module (http://drupal.org/project/ipaper), which does 3rd party integration with the www.scribd.com API. They have a great document display platform and the module I wrote is very comprehensive: it allows you to upload documents to scribd from your website, displays them using their ipaper application and even fetches full text for your own search index and thumbnails for your teaser views. Please help with testing if you are curious at all.

I want to finish one more functionality before I release a beta version and I need your help. I have an API call were I need to HTTP POST a Drupal file upload to scribd.com. It works well through CURL with having a parameter like 'file' => '@/home/drupal/files/file.txt', but I also want to offer support for those who don't have CURL.
I am trying to do it with drupal_http_request(), but I'm not even having success the non-file parameters across. Can anyone help me construct a POST with text (api_key, method) as well as binary (file) parameters? I understand that in order to do that I need to construct a 'multipart/form-data' request?

Thanks,
Rareş


/**
 * Provides for increased compatibility by sending requests either through CURL or fsockopen(drupal_http_request)
 */
function _ipaper_request($request_url, $params = NULL, $method = 'GET'){

  if (function_exists("curl_init")){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $request_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    if ($method == 'POST')
      curl_setopt($ch, CURLOPT_POST, 1 );
    if ($params)
      curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
    $data = curl_exec( $ch );
    curl_close($ch);
    return $data;
  }
  else{

    $headers = array();
      // ??? $headers = array('Content-Type' => 'multipart/form-data');
    $request = drupal_http_request($request_url . http_build_query($params), $headers, $method );
    // or maybe: $request = drupal_http_request($request_url, $headers, $method, http_build_query($params));
    if ($request->error)
      watchdog("ipaper", "Request failed - ". $request->error .' - '. http_build_query($params));
    return $request->data;
  }

}

Comments

c960657’s picture

I think you need to set Content-Type to application/x-www-form-urlencoded.

Also, according to the HTML specification, space should be encode as + rather than %20, and newlines should be encoded as %0D%0A rather than just %0A, but I am not sure it matters in practice.

rares’s picture


    $boundary = 'A0sFSD';
    $headers = array("Content-Type" => "multipart/form-data; boundary=$boundary");
    $request = drupal_http_request($request_url, $headers, $method, multipart_encode($boundary, $params));
    if ($request->error)
      watchdog("ipaper", "Request failed - ". $request->error .' - '. http_build_query($params));
    return $request->data;

function multipart_encode($boundary, $params){
  $output = "";
  foreach ($params as $key => $value){
    $output .= "--$boundary\r\n";
    if ($key == 'file'){
      $output .= multipart_enc_file($value);
    } else $output .= multipart_enc_text ($key, $value);
  }
  $output .="--$boundary--";
  return $output;
}

function multipart_enc_text($name, $value){
  return "Content-Disposition: form-data; name=\"$name\"\r\n\r\n$value\r\n";
}

function multipart_enc_file($path){
  if (substr($path, 0, 1) == "@") $path = substr($path, 1);
  $filename = basename($path);
  $mimetype = "application/octet-stream";
  $data = "Content-Disposition: form-data; name=\"file\"; filename=\"$filename\"\r\n";
  $data .= "Content-Transfer-Encoding: binary\r\n";
  $data .= "Content-Type: $mimetype\r\n\r\n";
  $data .= file_get_contents($path) . "\r\n";
  return $data;
}

--Rareş

marcvangend’s picture

Almost 4 years later, the syntax of drupal_http_request($url, $options) has changed a little, but your code example is still very useful. Thanks!

stefanotabarelli’s picture

I was looking for a solution and this post really helped me out. I think that a working example for D7 could help other people.
I had a form with 4 fields (text, select boxes and textarea) plus an attachment field. Here is my code:

// This is the attachment field in the form.
$form['attachment'] = array(
  '#type' => 'managed_file',
  '#name' => 'custom_content_block_image',
  '#title' => t('Attachment'),
  '#size' => 40,
  '#progress_indicator' => 'bar',
  '#upload_validators' => array(
    'file_validate_size' => array(variable_get('myfunction_form_upload_size', 2 * 1024 * 1024)),
  ),
  '#upload_location' => 'private://',
  '#description' => t('Upload a file, allowed extensions: .jpg, .jpeg, .gif, .png, .txt, .doc, .xls, .pdf, .ppt, .pps, .odt, .ods and .odp'),
);
/**
 * Generic function to perform a http request.
 */
function myfunction_contact_request($endpoint, $data, $method = 'POST') {
  $timeout = 30;
  $basepath = 'mybasepath';

  // Create a random boundary.
  $boundary = md5(uniqid('', TRUE));

  // Set the options for the request, including the encoded data.
  $options = array(
    'method' => 'POST',
    'data' => _myfunction_multipart_encode($boundary, $data->data),
    'timeout' => $timeout,
    'headers' => array('Content-Type' => "multipart/form-data; boundary=$boundary"),
  );
  return drupal_http_request(trim($basepath) . trim($endpoint), $options);
}

/**
 * Base function to encode a data array.
 *
 * @param string $boundary
  *   The boundary to use in the function.
 *
 * @return array
*   The values from the form.
 */
function _myfunction_multipart_encode($boundary, $params){
  $output = '';
  foreach ($params as $key => $value){
    $output .= "--$boundary\r\n";
    // If we have a file, we will encode that in a helper function.
    if ($key === 'file'){
      $output .= _myfunction_multipart_enc_file($value->uri);
    }
    // We do not haVe the file here, encode the text according to standards.
    else {
      $output .= _myfunction_multipart_enc_text($key, $value);
    }
  }
  $output .="--$boundary--";
  return $output;
}

/**
 * Function to multipart encode a file from a give path.
 *
 * @param array $path
 *   The path of the file.
 *
 * @return string
 *   A string with the mimetype and the file.
 */
function _myfunction_multipart_enc_file($path){
  if (substr($path, 0, 1) === '@') {
    $path = substr($path, 1);
  }
  // Lets get the filename.
  $filename = basename($path);
  // Set the mimetype to be a binary.
  $mimetype = "application/octet-stream";
  $data = "Content-Disposition: form-data; name=\"file\"; filename=\"$filename\"\r\n"; // "file" key.
  $data .= "Content-Transfer-Encoding: binary\r\n";
  $data .= "Content-Type: $mimetype\r\n\r\n";
  // Add the encoded file.
  $data .= file_get_contents($path) . "\r\n";
  return $data;
}

/**
 * Function to encode text data.
 *
 * @param string $name
 *   The name of the field.
 * @param string $value
 *   The value of the field.
 *
 * @return string
 *   A string encoded text data.
 */
function _myfunction_multipart_enc_text($name, $value){
  return "Content-Disposition: form-data; name=\"$name\"\r\n\r\n$value\r\n";
}
neibo’s picture

and how to use these wonderful functions?

mikeytown2’s picture

I just added support for sending files in HTTPRL. Example is in the issue queue: http://drupal.org/node/1780562#comment-6710636