GIT repositories

Index page of all the GIT repositories that are clonable form this server via HTTPS. Übersichtsseite aller GIT-Repositories, die von diesem Server aus über git clone (HTTPS) erreichbar sind.

Services

A bunch of service scripts to convert, analyse and generate data. Ein paar Services zum Konvertieren, Analysieren und Generieren von Daten.

GNU octave web interface

A web interface for GNU Octave, which allows to run scientific calculations from netbooks, tables or smartphones. The interface provides a web form generator for Octave script parameters with pre-validation, automatic script list generation, as well presenting of output text, figures and files in a output HTML page. Ein Webinterface für GNU-Octave, mit dem wissenschaftliche Berechnungen von Netbooks, Tablets oder Smartphones aus durchgeführt werden können. Die Schnittstelle beinhaltet einen Formulargenerator für Octave-Scriptparameter, mit Einheiten und Einfabevalidierung. Textausgabe, Abbildungen und generierte Dateien werden abgefangen und in einer HTML-Seite dem Nutzer als Ergebnis zur Verfügung gestellt.

C++ cURL Wrapper-Klassentemplate

C++ cURL API wrapper class template

Dieses Klassentemplate is nur eine sehr dünne Wrapperschicht um die C libcurl, deren Funktion in erster Linie darin besteht, die von cURL alloziierten Resourcen (über Destruktoren) wieder sicher freizugeben. Die restlichen (meisten) Methoden dienen einer Vereinfachung der API, so dass in modernen Entwicklungsumgebungen auch eine entsprechende Dokumentation angezeigt wird. Weiterhin werden Ein-/Ausgabekanäle auf std::istream bzw. std::ostream umgesetzt.

Das unten angegebene Beispielprogramm zeigt, wie die Klasse verwendet wird.

This class template is a very thin layer adound the C libcurl, and its main purpose to safely free resources allocated by 'cURL' (via destructors). The remaining (but actually must) methods represent readable, checked and documented forwards to the cURL API, so that modern IDEs show popup-documentation accordingly. Further functionality is to translate C i/o to c++ std::istream / std::ostream.

The example below depicts how to use this single-header-file implementation.

Dateien

Files

curl_request.hh example: curl_request.cc

Beispiele

Examples

/**
 * Examples for class sw::net::curl_http_request
 *
 * ------------------------------------------------------------------------------------
 * Example PHP script for localhost to see the request / response details
 * as plain text:
 *
 * <?php
 * $_PUT = "";
 * if($_SERVER['REQUEST_METHOD'] == "PUT") {
 *  $putdata = @fopen('php://input', 'r');
 *  while ($data = @fread($putdata, 1024)) $_PUT .= $data;
 *  @fclose($putdata);
 * }
 * $_REQUEST_HEADERS = getallheaders();
 * header('Content-Type: text/plain; charset=utf-8', true, 200);
 * print '$_SERVER=' . print_r($_SERVER, true);
 * print '$_REQUEST_HEADERS=' . print_r($_REQUEST_HEADERS, true) . "\n";
 * if(!empty($_GET)) print '$_GET=' . print_r($_GET, true) . "\n";
 * if(!empty($_POST)) print '$_POST=' . print_r($_POST, true) . "\n";
 * if(!empty($_SESSION)) print '$_SESSION=' . print_r($_SESSION, true) . "\n";
 * if(!empty($_COOKIE)) print '$_COOKIE=' . print_r($_COOKIE, true) . "\n";
 * if(!empty($_FILES)) print '$_FILES=' . print_r($_FILES, true) . "\n";
 * if(!empty($_PUT)) print '$_PUT=' . print_r($_PUT, true) . "\n";
 * ?>
 */
#include <net/curl_request.hh>
#include <sstream>
#include <string>
#include <iostream>
 
using namespace std;
using sw::net::curl_http_request;
 
// std::string svr_name_path = "localhost/";
std::string svr_name_path = "hades/hades/";
 
/**
 * GET request, directly print data to stdout.
 * Contains a lot of settings that are not required - show case ;)
 */
int example_get_stdout()
{
  // That's our instance, the stream to fetch the output to is cout here.
  curl_http_request curl(cout);
 
  // Every method call except .url() and .get() can actually be ommitted. cURL will
  // use proper default values.
  // If an error occurs somewhere in this setting sequence, all subsequent methods
  // will skip.
  string url = string("http://") + svr_name_path;
  curl
    .url(url)                       // URI to connect to
    .port(80)                       // Would be automatically 80
    .timeout(5)                     // 5s, for the whole transfer
    .connection_timeout(2)          // Timeout in seconds, only for connecting to the server
    .referrer("http://example.com") // Referer informatino in request header
    .http_version(1.1)              // That is the default, alternative is 1.0
    .keep_alive()                   // That is the default, for 1.1
    .range("0-")                    // Range(s) to fetch
    .cookie("a=1")                  // A random cookie to show you can set it.
    .cookie_jar("/tmp/mycookies")   // File to save cookies in / re-load from
    .max_redirections(3)            // Maximum number of redirs, can be ommitted, too
    .output_stream(cout)            // We did set that already in the c'tor, but it can be changed
    .user_agent("Mozilla/5.0 ...")  // User agent header
    .header("DNT: 1")               // Custom request headers are possible, too
    .get()                          // Make a get request.
  ;
 
  // curl_http_request has the operator !, indicating an error.
  if(!curl) {
    // error_text() -> string representation of the error code
    cerr << "[error  ] " << curl.error_text() << endl;
    // error_details() -> more detailed information what went wrong
    cerr << "[details] " << curl.error_details() << endl;
    return 1;
  }
 
  // Any allocated resources of the internal cURL C API will be freed in the destructor
  // of curl_http_request.
  return 0;
}
 
/**
 * Fetch data into a stringstream ...
 */
int example_get_stringstream()
{
  string url = string("http://") + svr_name_path;
  stringstream oss;
  curl_http_request curl(oss);
  if(!curl.url(url).get()) {
    cerr << "[error  ] " << curl.error_text() << endl;
    cerr << "[details] " << curl.error_details() << endl;
  } else {
    cout << "[output size] " << oss.str().length() << endl << endl;
    cout << oss.str() << endl;
    return 0;
  }
  return 1;
}
 
int example_ssl()
{
  string url = string("https://") + svr_name_path + "/";
  curl_http_request curl(cout);
  if(!curl
    .url(url)
    .verify_ssl(false)        // self signed allowed
    .verify_ssl(true)         // signed cert and the host name must match (the strict way)
    .verify_ssl(true, false)  // signed cert must exist (check only existence)
    .verify_ssl(true, true, false) // signed cert must be OK (no host name match)
    .verify_ssl()             // Equivalent to .verify_ssl(true) --> strict
    .verify_ssl(false)        // self signed allowed ---> for localhost
    .login("user", "pass")    // Authentication user/password
    .get()                    // Get request.
  ) {
    cerr << "[error  ] " << curl.error_text() << endl;
    cerr << "[details] " << curl.error_details() << endl;
    return 1;
  }
  return 0;
}
 
/**
 * "Normal" POST
 */
int example_post_urlencoded()
{
  string url = string("https://") + svr_name_path ;
  curl_http_request curl;
  ostringstream oss;
  string get_arg_value = "[[encoded GET value??]]";
  if(!curl
    .output_stream(oss)
    .url(url + "?test=" + curl.escape_url_string(get_arg_value))
    .port(443)
    .user_agent("Mozilla/5.0 Firefox/24.0")
    .login("me", "mypass")
    .referrer("http://hades/")
    .cookie("a=1000")
    .verify_ssl(false)
    .post_urlencoded("a", "10000")                // <--- Set POST variables
    .post_urlencoded("b-a-a", "+++  10000 +++ ")
    .post()
  ) {
    cerr << "[error  ] " << curl.error_text() << endl;
    cerr << "[details] " << curl.error_details() << endl;
    return 1;
  } else {
    cout << "[output size] " << oss.str().length() << endl << endl;
    cout << oss.str() << endl;
  }
  return 0;
}
 
/**
 * Multipart / formdata POST
 */
int example_post_multipart()
{
  string url = string("http://") + svr_name_path ;
  curl_http_request curl(cout);
  if(!curl
    .url(url)
    .post_multipart_string("key", "+++value+++")
    .post_multipart_file("file0.txt", "/tmp/file.txt") // content-type: auto === text/plain
    .post_multipart_file("file1.txt", "/tmp/file.txt", "text/html") // content-type: explicit
    .post()
  ) {
    cerr << "[error  ] " << curl.error_text() << endl;
    cerr << "[details] " << curl.error_details() << endl;
    return 1;
  }
  return 0;
}
 
/**
 * HTTP HEAD request (the short version)
 */
int example_head()
{ return !curl_http_request(cout).url(string("http://") + svr_name_path).head() ? 1 : 0; }
 
int example_put()
{
  string url = string("http://") + svr_name_path;
  ostringstream oss;
  istringstream iss("TEXT TEXT"); // <--- input stream for the PUT data
  curl_http_request curl(oss, iss);
  if(!curl
    .url(url)
    .ignore_content_length()       // Content-Length will be ommitted
    .ignore_content_length(false)  // Content-Length will be set (that's the default)
    .put(true)                     // true === "determine content length from stream by seeking"
                                   // to the end.
  ) {
    cerr << "[error  ] " << curl.error_text() << endl;
    cerr << "[details] " << curl.error_details() << endl;
    return 1;
  } else {
    cout << "[output size] " << oss.str().length() << endl << endl;
    cout << oss.str() << endl;
  }
  return 0;
}
 
int main(int argc, char** argv)
{
  int example = 0;
  if(argc >= 2 && argv[1] && argv[1][0] >= '0' && argv[1][0] <= '9') {
    example = ((int)argv[1][0]) - ((int)'0');
  }
 
  switch(example) {
    case 0: return example_get_stdout();
    case 1: return example_get_stringstream();
    case 2: return example_ssl();
    case 3: return example_post_urlencoded();
    case 4: return example_post_multipart();
    case 5: return example_head();
    case 6: return example_put();
    default: cerr << "0 to 6 :-)" << endl; return 1;
  }
}

Quelltext

Source code

/**
 * @package de.atwillys.cc.swl.net
 * @license BSD (simplified)
 * @author Stefan Wilhelm (stfwi)
 *
 * @file curl_request.hh
 * @ccflags
 * @ldflags -lcurl
 * @platform linux, bsd, windows
 * @standard >= c++98
 *
 * -----------------------------------------------------------------------------
 *
 * Minimal cURL template-based wrapper for interface homogenisation.
 *
 * You need libcurl.
 *
 * -----------------------------------------------------------------------------
 * +++ BSD license header +++
 * Copyright (c) 2011-2014, Stefan Wilhelm (stfwi, <cerbero s@atwilly s.de>)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met: (1) Redistributions
 * of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer. (2) Redistributions in binary form must reproduce
 * the above copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the distribution.
 * (3) Neither the name of atwillys.de nor the names of its contributors may be
 * used to endorse or promote products derived from this software without specific
 * prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 * -----------------------------------------------------------------------------
 */
#ifndef SW_CURL_REQUEST_HH
#define SW_CURL_REQUEST_HH
#include <curl/curl.h>
#include <string>
#include <iostream>
#include <vector>
 
// <editor-fold desc="curl_global_initializer" defaultstate="collapsed">
namespace sw { namespace net { namespace templates { namespace {
 
/**
 * Auxiliary class for global initialisation and cleanups.
 */
template <typename T=void>
class curl_global_initializer
{
public:
 
  ~curl_global_initializer()
  { curl_global_cleanup(); }
 
  static void init()
  { if(intance_.isinit_) return; intance_.isinit_=true; curl_global_init(CURL_GLOBAL_ALL); }
 
private:
  curl_global_initializer() : isinit_(false) { ; }
  curl_global_initializer(const curl_global_initializer&)  { ; }
  void operator=(const curl_global_initializer&)  { ; }
  volatile bool isinit_; // Prevent optimising this instance away
  static curl_global_initializer intance_;
};
 
template <typename T>
curl_global_initializer<T> curl_global_initializer<T>::intance_;
 
}}}}
// </editor-fold>
 
// <editor-fold desc="curl_request" defaultstate="collapsed">
namespace sw { namespace net { namespace templates {
 
template <typename S_t=void>
class curl_http_request
{
public:
 
  /**
   * Passthrough cURLtypes
   */
  typedef CURLoption curl_option_t;
  typedef CURLcode curl_error_t;
 
public:
 
  curl_http_request() : curl_(0), error_(CURLE_OK), ostream_(&std::cout), istream_(0),
          custom_headers_(0), form_begin_(0), form_end_(0)
  { init(); }
 
  explicit curl_http_request(std::ostream& os) : curl_(0), error_(CURLE_OK), ostream_(&os),
          istream_(0), custom_headers_(0), form_begin_(0), form_end_(0)
  { init(); }
 
  explicit curl_http_request(std::ostream& os, std::istream& is) : curl_(0), error_(CURLE_OK),
          ostream_(&os), istream_(&is), custom_headers_(0), form_begin_(0), form_end_(0)
  { init(); }
 
  ~curl_http_request()
  {
    if(curl_) curl_easy_cleanup(curl_);
    if(form_begin_) curl_formfree(form_begin_);
    if(custom_headers_) curl_slist_free_all(custom_headers_);
  }
 
public:
 
  /**
   * Returns true if the CURL instance has an error, false if it is OK.
   * @return bool
   */
  inline bool operator! ()
  { return error_ != CURLE_OK; }
 
  /**
   * Sets a curl option, returns a reference to itself.
   * @param curl_option_t opt
   * @param typename ValueType val
   * @return curl_request&
   */
  template <typename ValueType>
  curl_http_request& option(curl_option_t opt, ValueType val)
  { if(curl_ && error_ == CURLE_OK) { error_ = curl_easy_setopt(curl_, opt, val); } return *this; }
 
  /**
   * Returns the error text of the current CURL instance error, or an empty string if there
   * is no error.
   */
  inline std::string error_text() const throw()
  {
    if(error_ == CURLE_OK) return std::string();
    if(error_ < CURL_LAST) {
      const char* txt = curl_easy_strerror(error_);
      return txt ? txt : "(CURL error text is a nullptr!)";
    } else if(error_ == CURL_LAST+1) {
      return "Setting custom header failed";
    } else if(error_ == CURL_LAST+2) {
      return "Adding form data failed";
    } else if(error_ == CURL_LAST+3) {
      return "Mixed multipart and urlencoded form data not allowed";
    }
    return "Unknown error";
  }
 
  /**
   * Returns the error text of the current CURL instance error, or an empty string if there
   * is no error. The error message may differ from and be more specific than error_text().
   */
  inline std::string error_details() const throw()
  {
    if(error_ == CURLE_OK) return std::string();
    const_cast<char&>(error_buffer_[CURL_ERROR_SIZE]) = '\0';
    return error_buffer_;
  }
 
  /**
   * Returns the current error code of the CURL instance. 0===CURLE_OK for "no error".
   * @return curl_error_t
   */
  inline curl_error_t error() const throw()
  { return error_; }
 
  /**
   * Resets/re-initialises the CURL instance.
   * @return curl_request&
   */
  curl_http_request& clear()
  {
    error_ = 0;
    error_buffer_[0] = 0;
    if(curl_) curl_easy_cleanup(curl_);
    if(custom_headers_) { curl_slist_free_all(custom_headers_); custom_headers_ = NULL; }
    if(form_begin_) { curl_formfree(form_begin_); form_begin_ = form_end_ = NULL; }
    error_ = (!(curl_ = curl_easy_init())) ? CURLE_FAILED_INIT : CURLE_OK;
    return *this;
  }
 
  /**
   * Sets the input stream for sending data to the server.
   * @param std::istream& is
   * @return curl_http_request&
   */
  curl_http_request& input_stream(std::istream& is)
  { istream_ = &is; return *this; }
 
  /**
   * Sets the output stream where to receive the server response to.
   * @param std::ostream& os
   * @return curl_http_request&
   */
  curl_http_request& output_stream(std::ostream& os)
  { ostream_ = &os; return *this; }
 
public:
 
  /**
   * Set URL
   * @param const std::string&
   * @return curl_request&
   */
  inline curl_http_request& url(const std::string& s)
  { return option(CURLOPT_URL, s.c_str() ); }
 
  /**
   * Sets the port to connect to.
   * @param long
   * @return curl_request&
   */
  inline curl_http_request& port(long l)
  { return option(CURLOPT_PORT, l); }
 
  /**
   * Sets the connection/read timeout in seconds.
   * @param long
   * @return curl_request&
   */
  inline curl_http_request& timeout(long l)
  { return option(CURLOPT_TIMEOUT, l); }
 
  /**
   * Sets the connection timeout in seconds.
   * @param long
   * @return curl_request&
   */
  inline curl_http_request& connection_timeout(long l)
  { return option(CURLOPT_CONNECTTIMEOUT, l); }
 
  /**
   * Sets the maximum number of redirections.
   * @param long
   * @return curl_request&
   */
  inline curl_http_request& max_redirections(long l)
  { return option(CURLOPT_MAXREDIRS, l); }
 
  /**
   * Set user agents string.
   * @param const std::string&
   * @return curl_request&
   */
  inline curl_http_request& user_agent(const std::string& s)
  { return option(CURLOPT_USERAGENT, s.c_str() ); }
 
  /**
   * Set referrer string.
   * @param const std::string&
   * @return curl_request&
   */
  inline curl_http_request& referrer(const std::string& s)
  { return option(CURLOPT_REFERER, s.c_str() ); }
 
  /**
   * Set the user password for login information.
   * @param const std::string& user
   * @param const std::string& pass
   * @return curl_request&
   */
  inline curl_http_request& login(const std::string& user, const std::string& pass)
  {
    std::string s = user; s += ":"; s += pass;
    return option(CURLOPT_USERPWD, s.c_str() );
  }
 
  /**
   * Sets the content range to request form the server, e.g. "0-99,200-299"
   * @param const std::string&
   * @return curl_request&
   */
  inline curl_http_request& range(const std::string& s)
  { return option(CURLOPT_RANGE, s.c_str() ); }
 
  /**
   * Sets the cookie string sent in the request header.
   * @param const std::string&
   * @return curl_request&
   */
  inline curl_http_request& cookie(const std::string& s)
  { return option(CURLOPT_COOKIE, s.c_str() ); }
 
  /**
   * Sets a file name/path where cURL will save cookie data in.
   * @param const std::string&
   * @return curl_request&
   */
  inline curl_http_request& cookie_jar(const std::string& s)
  { return option(CURLOPT_COOKIEJAR, s.c_str() ); }
 
  /**
   * Sets version of the HTTP request. 1.0 for HTTP/1.0, 1.1 for HTTP/1.1 etc.
   * @param double version
   * @return curl_request&
   */
  inline curl_http_request& http_version(double v)
  { return option(CURLOPT_HTTP_VERSION, (long) (v==1.0 ? CURL_HTTP_VERSION_1_0 :
           CURL_HTTP_VERSION_1_1)); }
 
  /**
   * Sets the connection keep-alive option.
   * @param bool
   * @return curl_request&
   */
  inline curl_http_request& keep_alive(bool b=true)
  { return option(CURLOPT_TCP_KEEPALIVE, b ? 1 : 0); }
 
  /**
   * Adds a custom request header, e.g. curl.header("DNT: 1");
   * @param const std::string& field
   * @return curl_http_request&
   */
  inline curl_http_request& header(const std::string& field)
  {
    if(error_ != CURLE_OK) return *this;
    custom_headers_ = curl_slist_append(custom_headers_, field.c_str());
    if(!custom_headers_) {
      error_ = static_cast<curl_error_t>((long)CURL_LAST+1);
      std::string s = "Failed to set header: '"; s += field; s += "'";
      if(s.length() >= CURL_ERROR_SIZE) s.resize(CURL_ERROR_SIZE-1);
      std::string::size_type i;
      for(i=0; i<s.length(); ++i) error_buffer_[i] = s[i];
      error_buffer_[i] = error_buffer_[CURL_ERROR_SIZE-1] = '\0';
    }
    return *this;
  }
 
  /**
   * Sets the response data size does not have to match the Content-Length header field.
   * @param bool
   * @return curl_request&
   */
  inline curl_http_request& ignore_content_length(bool b=true)
  { return option(CURLOPT_IGNORE_CONTENT_LENGTH, b ? 1 : 0); }
 
  /**
   * Sets if the response headers shall be included in the response data.
   * @param bool
   * @return curl_request&
   */
  inline curl_http_request& prepend_response_header(bool b=true)
  { return option(CURLOPT_HEADER, b ? 1 : 0); }
 
  /**
   * Set the proxy to use.
   * @param const std::string&
   * @return curl_request&
   */
  inline curl_http_request& proxy(const std::string& s)
  { return option(CURLOPT_PROXY, s.c_str() ); }
 
  /**
   * Set the user password for login information.
   * @param const std::string& user
   * @param const std::string& pass
   * @return curl_request&
   */
  inline curl_http_request& proxy_user_password(const std::string& user, const std::string& pass)
  {
    std::string s = user; s += ":"; s += pass;
    return option(CURLOPT_PROXYUSERPWD, s.c_str() );
  }
 
  /**
   * Sets weather cURL shall check the SSL certificate for validity.
   * @param bool check=true
   * @param bool check_host_match=true
   * @return curl_request&
   */
  inline curl_http_request& verify_ssl(bool check=true, bool cert=true, bool host_match=true)
  {
    if(!check) return option(CURLOPT_SSL_VERIFYHOST, 0L).option(CURLOPT_SSL_VERIFYPEER, 0L);
    return option(CURLOPT_SSL_VERIFYPEER, 1L).option(CURLOPT_SSL_VERIFYHOST, (long)(cert ?
                 (host_match ? 2:1):0));
  }
 
public:
 
  /**
   * Add a string value to the form data (multipart) for a POST request later.
   * @param const std::string& key
   * @param const std::string& value
   * @return curl_http_request&
   */
  curl_http_request& post_multipart_string(const std::string& key, const std::string& value)
  {
    if(!error_) post_push_check(curl_formadd(&form_begin_, &form_end_,
      CURLFORM_COPYNAME, key.c_str(),
      CURLFORM_COPYCONTENTS, value.c_str(),
      CURLFORM_END)
    );
    return *this;
  }
 
  /**
   * Add a file path to be uploaded via POST to the form data (multipart).
   * @param const std::string& key
   * @param const std::string& path
   * @param std::string content_type=""
   * @return curl_http_request&
   */
  curl_http_request& post_multipart_file(const std::string& key, const std::string& path,
          std::string content_type="")
  {
    if(error_ != CURLE_OK) return *this;
    if(!content_type.empty()) {
      post_push_check(curl_formadd(&form_begin_, &form_end_,
        CURLFORM_COPYNAME, key.c_str(),
        CURLFORM_FILE, path.c_str(),
        CURLFORM_CONTENTTYPE, content_type.c_str(),
        CURLFORM_END)
      );
    } else {
      post_push_check(curl_formadd(&form_begin_, &form_end_, CURLFORM_COPYNAME, key.c_str(),
                     CURLFORM_FILE, path.c_str(), CURLFORM_END));
    }
    return *this;
  }
 
  /**
   * Add key-value pair to an url-encoded ("normal") POST
   * @param std::string key
   * @param std::string value
   * @return curl_http_request&
   */
  curl_http_request& post_urlencoded(std::string key, std::string value)
  {
    if(error_ != CURLE_OK) return *this;
    if(form_data_.empty()) {
      form_data_ = escape_url_string(key) + "=" + escape_url_string(value);
    } else {
      form_data_ += "&";
      form_data_ += escape_url_string(key);
      form_data_ += "=";
      form_data_ += escape_url_string(value);
    }
    return *this;
  }
 
public:
 
  /**
   * Perform a GET request
   * @return curl_request&
   */
  curl_http_request& get()
  { return option(CURLOPT_HTTPGET, 1).run(); }
 
  /**
   * Perform a HEAD request
   * @return curl_request&
   */
  curl_http_request& head()
  { return option(CURLOPT_HTTPGET, 1).option(CURLOPT_NOBODY, 1).run(); }
 
  /**
   * Perform a POST request
   * @return curl_request&
   */
  curl_http_request& post(bool expect_100_continue=false)
  {
    if(!curl_ || error_ != CURLE_OK) return *this;
    if(form_begin_ && !form_data_.empty()) {
      error_ = (curl_error_t)((long)CURL_LAST+3);
      // no strncpy --> the very old POD way ...
      const char* errtxt = "Multipart and urlencoded form data mixed.";
      for(int i=0; i<CURL_ERROR_SIZE && errtxt[i]; ++i) error_buffer_[i] = errtxt[i];
      error_buffer_[i] = error_buffer_[CURL_ERROR_SIZE] = '\0'; // sz of e_buf_ > CURL_E_SZ
      return *this;
    } else if(!form_data_.empty()) {
      return option(CURLOPT_POST, 1).option(CURLOPT_POSTFIELDS, form_data_.c_str()).run();
    } else {
      if(!expect_100_continue) header("Expect:");
      return option(CURLOPT_POST, 1).option(CURLOPT_HTTPPOST, form_begin_).run();
    }
  }
 
  /**
   * Perform a PUT request
   * @param bool seek_stream_size = false
   * @return curl_request&
   */
  curl_http_request& put(bool seek_stream_size=false)
  {
    option(CURLOPT_PUT, 1);
    if(istream_) {
      option(CURLOPT_UPLOAD, 1);
      if(error_ == CURLE_OK && seek_stream_size) {
        istream_->seekg(0, std::istream::end);
        std::istream::off_type sz = istream_->tellg();
        istream_->seekg(0, std::istream::beg);
        option(CURLOPT_INFILESIZE, static_cast<long>(sz));
      }
    }
    return run();
  }
 
public:
 
  /**
   * Converts formatted date string to a Unix timestamp.
   * @param const std::string& date
   * @return time_t
   */
  static time_t date2timestamp(const std::string& date)
  {
    std::vector<char> d(date.begin(), date.end());
    d.push_back(0);
    time_t t0;
    return curl_getdate(&d[0], &t0);
  }
 
  /**
   * URL encodes a string
   * @param const std::string& s
   * @return time_t
   */
  std::string escape_url_string(const std::string& url)
  {
    std::string s;
    s.reserve(url.size() * 2);
    char* v = curl_easy_escape(curl_, url.c_str(), url.length());
    if(v) s = v;
    curl_free(v);
    return s;
  }
 
  /**
   * URL decodes a string
   * @param const std::string& s
   * @return time_t
   */
  std::string unescape_url_string(const std::string& url)
  {
    std::string s;
    s.reserve(url.length());
    int len;
    char* v = curl_easy_unescape(curl_, url.c_str(), url.length(), &len);
    if(v) for(unsigned i=0; i<(unsigned)len; ++i) s.push_back(v[i]);
    curl_free(v);
    return s;
  }
 
private:
 
  // Prevent copy construction or assignment
  explicit curl_http_request(const curl_http_request&) { ; }
  void operator= (const curl_http_request&) { ; }
 
  /**
   * cURL initialisation, called from public constructors
   */
  void init() {
    error_buffer_[0] = error_buffer_[CURL_ERROR_SIZE] = '\0';
    curl_global_initializer<>::init();
    if(!(curl_ = curl_easy_init())) { error_ = CURLE_FAILED_INIT; return; }
    if(!option(CURLOPT_ERRORBUFFER, error_buffer_)) return;
    if(!option(CURLOPT_NOPROGRESS, 1L)) return;
    if(!option(CURLOPT_WRITEDATA, reinterpret_cast<void*>(this))) return;
    if(!option(CURLOPT_WRITEFUNCTION, write_callback)) return;
    if(!option(CURLOPT_READDATA, reinterpret_cast<void*>(this))) return;
    if(!option(CURLOPT_READFUNCTION, read_callback)) return;
  }
 
  curl_http_request& run()
  {
    if(custom_headers_) option(CURLOPT_HTTPHEADER, custom_headers_);
    if(curl_ && error_ == CURLE_OK) { error_ = curl_easy_perform(curl_); }
    return *this;
  }
 
  /**
   * Write function, must be noexcept because it is a C callback.
   */
  static size_t write_callback(void *data, size_t sz, size_t csz, void *that) throw()
  {
    size_t size = sz * csz;
    if(!size) return 0;
    try {
      curl_http_request* me = reinterpret_cast<curl_http_request*>(that);
      if(!me || !me->ostream_ || !data) return 0;
      me->ostream_->write(reinterpret_cast<const char*>(data), size);
      return me->ostream_->good() ? size : 0;
    } catch(...) {
      return 0; // no exceptions allowed in a C callback
    }
    return 0;
  }
 
  /**
   * Read function
   */
  static size_t read_callback(void *data, size_t sz, size_t csz, void *that)
  {
    size_t size = sz * csz;
    if(!size) return 0;
    try {
      curl_http_request* me = reinterpret_cast<curl_http_request*>(that);
      if(!me || !me->istream_ || !data) return CURL_READFUNC_ABORT;
      if(me->istream_->eof()) return 0;
      size_t nread = (size_t) me->istream_->readsome(reinterpret_cast<char*>(data), size);
      if(!me->istream_->good() && !me->istream_->eof()) return CURL_READFUNC_ABORT;
      return nread;
    } catch(...) {
      return CURL_READFUNC_ABORT;
    }
  }
 
  /**
   * POST form data push
   */
  void post_push_check(CURLFORMcode code)
  {
    const char *err = "";
    switch(code) {
      case CURL_FORMADD_OK: return;
      case CURL_FORMADD_MEMORY: err = "Out of memory"; break;
      case CURL_FORMADD_OPTION_TWICE: err = "Key specified twice"; break;
      case CURL_FORMADD_NULL: err = "nullptr given"; break;
      case CURL_FORMADD_UNKNOWN_OPTION: err = "Unknown option"; break;
      case CURL_FORMADD_INCOMPLETE: err = "Form data incomplete"; break;
      case CURL_FORMADD_ILLEGAL_ARRAY: err = "Illegal array"; break;
      case CURL_FORMADD_DISABLED: err = "POST form feature disabled"; break;
      default: err = "Unspecified form add error"; break;
    }
    error_ = (curl_error_t)((int)CURL_LAST+2);
    int i;
    for(i=0; i<CURL_ERROR_SIZE && err[i]; ++i) error_buffer_[i] = err[i];
    error_buffer_[i] = '\0'; // sizeof error_buffer_ > CURL_ERROR_SIZE
  }
 
private:
 
  typedef struct curl_slist* curl_headers_list_t;
  typedef struct curl_httppost curl_httppost_t;
 
  CURL* curl_;
  curl_error_t error_;
  std::ostream *ostream_;
  std::istream *istream_;
  char error_buffer_[CURL_ERROR_SIZE+1];
  curl_headers_list_t custom_headers_;
  curl_httppost_t *form_begin_, *form_end_; // multipart
  std::string form_data_; // urlencoded
};
 
}}}
 
namespace sw { namespace net {
  typedef templates::curl_http_request<> curl_http_request;
}}
// </editor-fold>
 
#endif