1. HTTPS with OpenSSL

Here is an attempt to let OpenEuphoria manage HTTPS with help of OpenSSL. OpenSSL Windows binaries (openssl.exe, libeay32.dll and ssleay32.dll) have to be in the same directory as following source code.

I get something, but I'm not fully sure it works. Help needed.

  • std/https.e
--**** 
-- == HTTPS Client 
-- 
-- <<LEVELTOC level=2 depth=4>> 
-- 
 
namespace https 
 
include std/base64.e 
include std/convert.e 
include std/rand.e 
include std/sequence.e 
include std/text.e 
include std/types.e 
include std/pipeio.e as pipe 
 
 
include std/net/dns.e 
include std/net/url.e as url 
 
include euphoria/info.e 
 
constant USER_AGENT_HEADER =  
	sprintf("User-Agent: Euphoria-HTTP/%d.%d\r\n", { 
		version_major(), version_minor() }) 
 
enum R_HOST, R_PORT, R_PATH, R_REQUEST 
constant FR_SIZE = 4 
 
--**** 
-- === Error Codes 
-- 
 
public enum by -1 
	ERR_MALFORMED_URL = -1, 
	ERR_INVALID_PROTOCOL, 
	ERR_INVALID_DATA, 
	ERR_INVALID_DATA_ENCODING, 
	ERR_HOST_LOOKUP_FAILED, 
	ERR_CONNECT_FAILED, 
	ERR_SEND_FAILED, 
	ERR_RECEIVE_FAILED 
 
--**** 
-- === Constants 
 
public enum 
	FORM_URLENCODED, 
	MULTIPART_FORM_DATA 
	 
public enum 
	--** 
	-- No encoding is necessary 
	ENCODE_NONE = 0, 
	--** 
	-- Use Base64 encoding 
	ENCODE_BASE64 
 
constant ENCODING_STRINGS = { 
	"application/x-www-form-urlencoded", 
	"multipart/form-data" 
} 
 
object command_pipe=-1, openssl_process=-1 
-- 
-- returns: { host, port, path, base_reqest } 
-- 
 
function format_base_request(sequence request_type, sequence url, object headers) 
	sequence request = "" 
	sequence formatted_request 
	integer noport = 0 
 
	object parsedUrl = url:parse(url) 
	if atom(parsedUrl) then 
		return ERR_MALFORMED_URL 
	elsif not equal(parsedUrl[URL_PROTOCOL], "https") then 
		return ERR_INVALID_PROTOCOL 
	end if 
 
	sequence host = parsedUrl[URL_HOSTNAME] 
 
	integer port = parsedUrl[URL_PORT] 
	if port = 0 then 
		port = 80 
		noport = 1 
	end if 
 
	sequence path 
	if sequence(parsedUrl[URL_PATH]) then 
		path = parsedUrl[URL_PATH] 
	else 
		path = "/" 
	end if 
 
	if sequence(parsedUrl[URL_QUERY_STRING]) then 
		path &= "?" & parsedUrl[URL_QUERY_STRING] 
	end if 
 
	-- only specify the port in the request if the caller did so explicitly 
	-- some sites, such as euphoria.pastey.net, will break otherwise 
	if noport then 
		request = sprintf("%s %s HTTP/1.0\r\nHost: %s\r\n", { 
			request_type, url, path, host }) 
	else 
		request = sprintf("%s %s HTTP/1.0\r\nHost: %s:%d\r\n", { 
			request_type, url, host, port }) 
	end if 
 
	integer has_user_agent = 0 
	integer has_connection = 0 
 
	if sequence(headers) then 
		for i = 1 to length(headers) do 
			object header = headers[i] 
			if equal(header[1], "User-Agent") then 
				has_user_agent = 1 
			elsif equal(header[1], "Connection") then 
				has_connection = 1 
			end if 
 
			request &= sprintf("%s: %s\r\n", header) 
		end for 
	end if 
 
	if not has_user_agent then 
		request &= USER_AGENT_HEADER 
	end if 
	if not has_connection then 
		request &= "Connection: close\r\n" 
	end if 
 
	formatted_request = repeat(0, FR_SIZE) 
	formatted_request[R_HOST] = host 
	formatted_request[R_PORT] = port 
	formatted_request[R_PATH] = path 
	formatted_request[R_REQUEST] = request 
	return formatted_request 
end function 
 
-- 
-- encode a sequence of key/value pairs 
-- 
 
function form_urlencode(sequence kvpairs) 
	sequence data = "" 
 
	for i = 1 to length(kvpairs) do 
		object kvpair = kvpairs[i] 
 
		if i > 1 then 
			data &= "&" 
		end if 
 
		data &= kvpair[1] & "=" & url:encode(kvpair[2]) 
	end for 
 
	return data 
end function 
 
function multipart_form_data_encode(sequence kvpairs, sequence boundary) 
	sequence data = "" 
	 
	for i = 1 to length(kvpairs) do 
		object kvpair = kvpairs[i] 
 
		integer enctyp = ENCODE_NONE 
		sequence mimetyp = "" 
		 
		if i > 1 then 
			data &= "\r\n" 
		end if 
		 
		data &= "--" & boundary & "\r\n" 
		data &= "Content-Disposition: form-data; name=\"" & kvpair[1] & "\"" 
		if length(kvpair) = 5 then 
			data &= "; filename=\"" & kvpair[3] & "\"\r\n" 
			data &= "Content-Type: " & kvpair[4] & "\r\n" 
			 
			switch kvpair[5] do 
				case ENCODE_NONE then 
				case ENCODE_BASE64 then 
					data &= "Content-Transfer-Encoding: base64\r\n" 
					kvpair[2] = base64:encode(kvpair[2], 76) 
			end switch 
		else 
			data &= "\r\n" 
		end if	 
			 
		data &= "\r\n" & kvpair[2] 
	end for 
 
	return data & "\r\n--" & boundary & "--" 
end function 
 
constant rand_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 
constant rand_chars_len = length(rand_chars) 
 
function random_boundary(integer len) 
	sequence boundary = repeat(0, len) 
	 
	for i = 1 to len do 
		boundary[i] = rand_chars[rand(rand_chars_len)] 
	end for 
	 
	return boundary 
end function 
 
public function run_openssl(sequence dest) 
  command_pipe = pipe:create() 
  openssl_process = pipe:exec("openssl s_client -connect " & dest, command_pipe) 
  if atom(openssl_process) then 
    printf(1, "Failed to exec() with error %x", pipe:error_no()) 
    pipe:kill(openssl_process) 
    return 0 
  end if 
  return 1 
end function 
 
function write_openssl(sequence data) 
  return pipe:write(openssl_process[STDIN], data) 
end function 
 
function read_openssl() 
  sequence data = "" 
  object c = pipe:read(openssl_process[pipe:STDOUT], 256) 
  while sequence(c) and length(c) do 
    data &= c 
    if atom(c) then 
      printf(1, "Failed on read with error %x", pipe:error_no()) 
      pipe:kill(openssl_process) 
      return 0 
    end if 
    c = pipe:read(openssl_process[pipe:STDOUT], 256) 
  end while 
  return data 
end function 
 
public procedure close_openssl() 
  pipe:kill(openssl_process) 
end procedure 
 
-- 
-- Send an HTTPS request 
-- 
 
function execute_request(sequence request, integer timeout) 
	if write_openssl(request) != length(request) then 
		return ERR_SEND_FAILED 
	end if 
 
	atom start_time = time() 
	integer got_header = 0, content_length = 0 
	sequence content = "" 
	sequence headers = {} 
	while time() - start_time < timeout label "top" do 
		if got_header and length(content) = content_length then 
			exit 
		end if 
 
		object data = read_openssl() 
		if atom(data) then 
			return ERR_RECEIVE_FAILED 
		end if 
		if length(data) = 0 then 
			-- zero bytes received, we the 'data' waiting was 
			-- a disconnect. 
			exit "top" 
  		end if 
 
		content &= data 
 
		if not got_header then 
			integer header_end_pos = match("\r\n\r\n", content) 
			if header_end_pos then 
				-- we have a header, let's parse it and figure out 
				-- the content length. 
				sequence raw_header = content[1..header_end_pos] 
				content = content[header_end_pos + 4..$] 
 
				sequence header_lines = split(raw_header, "\r\n") 
				headers = append(headers, split(header_lines[1], " ")) 
				for i = 2 to length(header_lines) do 
					object header = header_lines[i] 
					sequence this_header = split(header, ": ", , 1) 
					this_header[1] = lower(this_header[1]) 
					headers = append(headers, this_header) 
 
					if equal(this_header[1], "content-length") then 
						content_length = to_number(this_header[2]) 
					end if 
				end for 
 
				got_header = 1 
			end if 
		end if 
	end while 
 
	return { headers, content } 
end function 
 
--**** 
-- === Get/Post Routines 
 
--** 
-- Post data to a HTTP resource. 
-- 
-- Parameters: 
--   * ##url##     - URL to send post request to 
--   * ##data##    - Form data (described later) 
--   * ##headers## - Additional headers added to request 
--   * ##follow_redirects## - Maximum redirects to follow 
--   * ##timeout## - Maximum number of seconds to wait for a response 
-- 
-- Returns: 
--   An integer error code or a 2 element sequence. Element 1 is a sequence 
--   of key/value pairs representing the result header information. element 
--   2 is the body of the result. 
-- 
--   If result is a negative integer, that represents a local error condition. 
-- 
--   If result is a positive integer, that represents a HTTP error value from 
--   the server. 
-- 
-- Data Sequence: 
--  This sequence should contain key value pairs representing the expected form 
--  elements of the called URL. For a simple url-encoded form: 
-- 
--  { {"name", "John Doe"}, {"age", "22"}, {"city", "Small Town"}} 
 
-- 
--  All Keys and Values should be a sequence. 
-- 
--  If the post requires multipart form encoding then the sequence is a little 
--  different. The first element of the data sequence must be [[:MULTIPART_FORM_DATA]]. 
--  All subsequent field values should be key/value pairs as described above **except** 
--  for a field representing a file upload. In that case the sequence should be: 
-- 
--  ##{ FIELD-NAME, FILE-VALUE, FILE-NAME, MIME-TYPE, ENCODING-TYPE }## 
-- 
--  Encoding type can be 
--    * [[:ENCODE_NONE]] 
--    * [[:ENCODE_BASE64]] 
-- 
--  An example for a multipart form encoded post request data sequence 
-- 
--  {  
--    { "name", "John Doe" },  
--    { "avatar", file_content, "me.png", "image/png", ENCODE_BASE64 }, 
--    { "city", "Small Town" } 
--  } 
 
-- 
-- See Also: 
--   [[:http_get]] 
-- 
 
public function https_post(sequence url, object data, object headers = 0, 
		integer follow_redirects = 10, integer timeout = 15) 
		 
	follow_redirects = follow_redirects -- Not used yet. 
	 
	if not sequence(data) or length(data) = 0 then 
		return ERR_INVALID_DATA 
	end if 
 
	object request = format_base_request("POST", url, headers) 
	if atom(request) then 
		return request 
	end if 
 
	integer data_type 
	if ascii_string(data) or sequence(data[1]) then 
		data_type = FORM_URLENCODED 
	else 
		if data[1] < 1 or data[1] > 2 then 
			return ERR_INVALID_DATA_ENCODING 
		end if 
 
		data_type = data[1] 
		data = data[2] 
	end if 
 
	-- data now contains either a string sequence already encoded or 
	-- a sequence of key/value pairs to be encoded. We know the length 
	-- is greater than 0, so check the first element to see if it's a 
	-- sequence or an atom. That will tell us what we have. 
	-- 
	-- If we have key/value pairs then we will need to encode that data 
	-- according to our data_type. 
 
	sequence content_type = ENCODING_STRINGS[data_type] 
	if sequence(data[1]) then 
		-- We have key/value pairs 
		if data_type = FORM_URLENCODED then 
			data = form_urlencode(data) 
		else 
			sequence boundary = random_boundary(20) 
			content_type &= "; boundary=" & boundary 
			data = multipart_form_data_encode(data, boundary) 
		end if 
	end if 
 
	request[R_REQUEST] &= sprintf("Content-Type: %s\r\n", { content_type }) 
	request[R_REQUEST] &= sprintf("Content-Length: %d\r\n", { length(data) }) 
	request[R_REQUEST] &= "\r\n" 
	request[R_REQUEST] &= data 
 
	return execute_request(request[R_REQUEST], timeout) 
end function 
 
--** 
-- Get a HTTP resource. 
-- 
-- Returns: 
--   An integer error code or a 2 element sequence. Element 1 is a sequence 
--   of key/value pairs representing the result header information. Element 
--   2 is the body of the result. 
-- 
--   If result is a negative integer, that represents a local error condition. 
-- 
--   If result is a positive integer, that represents a HTTP error value from 
--   the server. 
-- 
-- Example: 
-- 
-- include std/console.e -- for display() 
-- include std/net/http.e 
-- 
-- object result = http_get("http://example.com")  
-- if atom(result) then  
--    printf(1, "Web error: %d\n", result)  
--     abort(1)  
-- end if  
--  
-- display(result[1]) -- header key/value pairs 
-- printf(1, "Content: %s\n", { result[2] })  
 
-- 
-- See Also: 
--   [[:http_post]] 
-- 
 
public function https_get(sequence url, object headers = 0, integer follow_redirects = 10, 
		integer timeout = 15) 
	object request = format_base_request("GET", url, headers) 
	 
	follow_redirects = follow_redirects -- Not used yet. 
	 
	if atom(request) then 
		return request 
	end if 
 
	-- No more work necessary, terminate the request with our ending CR LF 
	request[R_REQUEST] &= "\r\n" 
 
	return execute_request(request[R_REQUEST], timeout) 
end function 
  • test program
include std/types.e 
include std/console.e 
include std/io.e 
include std/https.e 
 
integer offset = 0 
 
procedure dumpHeader(object x, sequence Name, integer Output=1, sequence path={}, integer level=0, sequence prefix="") 
-- prints a sequence structure in a human readable way 
  integer subSequence 
  sequence s, offset 
 
  if level=0 then printf(Output, "%s = ", {Name}) end if 
  offset = "" 
  for i = 1 to level do offset &= ".  " end for 
  s = "" 
  if t_display(x) then 
    if length(x) = 0 then 
      s = " \"\"" 
    else 
      s = sprintf(" \"%s\"", {x}) 
    end if 
    puts(Output, offset&prefix&s&"\n") 
  else 
    subSequence = 0 
    for i=1 to length(x) do 
      if sequence(x[i]) then 
        subSequence = 1 
        exit 
      end if 
    end for 
    if subSequence = 0 then 
      s = sprintf(" %s", {x}) 
      puts(Output, offset&prefix&s&"\n") 
    else 
      if length(s) = 0 then 
        puts(Output, offset&prefix&"\n") 
      end if 
      for i=1 to length(x) do 
        prefix = sprintf("[%d]", i) 
        dumpHeader(x[i], Name, Output, path&sprintf("[%d]", i), level+1, prefix) 
      end for 
    end if 
  end if 
  if Output > 2 then flush(Output) end if 
end procedure 
 
  if run_openssl("gw.geneanet.org:443") then 
--  sequence s = read_openssl() 
    object result = https_get("https://gw.geneanet.org/connexion/")  
    if atom(result) then  
      printf(1, "Web error: %d\n", result)  
      maybe_any_key() 
      abort(1)  
    end if  
    dumpHeader(result[1], "header") 
    puts(1, result[2] & "\n") 
    close_openssl() 
  end if 
 
maybe_any_key() 

Jean-Marc

new topic     » topic index » view message » categorize

2. Re: HTTPS with OpenSSL

It would probably be better to wrap the shared libraries instead of calling out to an external process.

I would also recommend using libcurl instead of calling OpenSSL directly. I'm surprised we don't have a better wrapper in The Archive.

I'll see if that's something I can knock out quickly. Their page on Euphoria also needs updating since Ray Smith left our community long ago.

-Greg

new topic     » goto parent     » topic index » view message » categorize

3. Re: HTTPS with OpenSSL

ghaberek said...

I'll see if that's something I can knock out quickly. Their page on Euphoria also needs updating since Ray Smith left our community long ago.

Ugh. I forgot how craptacular the CURLOPT macros are. The authors even refer to this as macro-mania.

/* 
 * This macro-mania below setups the CURLOPT_[what] enum, to be used with 
 * curl_easy_setopt(). The first argument in the CINIT() macro is the [what] 
 * word. 
 */ 
 
typedef enum { 
  /* This is the FILE * or void * the regular output should be written to. */ 
  CINIT(WRITEDATA, OBJECTPOINT, 1), 
 
  /* The full URL to get/put */ 
  CINIT(URL, STRINGPOINT, 2), 
 
  /* Port number to connect to, if other than default. */ 
  CINIT(PORT, LONG, 3), 
 
  /* Name of proxy to use. */ 
  CINIT(PROXY, STRINGPOINT, 4), 
 
  ... 

-Greg

new topic     » goto parent     » topic index » view message » categorize

4. Re: HTTPS with OpenSSL

ghaberek said...

Ugh. I forgot how craptacular the CURLOPT macros are. The authors even refer to this as macro-mania.

/* 
 * This macro-mania below setups the CURLOPT_[what] enum, to be used with 
 * curl_easy_setopt(). The first argument in the CINIT() macro is the [what] 
 * word. 
 */ 
 
typedef enum { 
  /* This is the FILE * or void * the regular output should be written to. */ 
  CINIT(WRITEDATA, OBJECTPOINT, 1), 
 
  /* The full URL to get/put */ 
  CINIT(URL, STRINGPOINT, 2), 
 
  /* Port number to connect to, if other than default. */ 
  CINIT(PORT, LONG, 3), 
 
  /* Name of proxy to use. */ 
  CINIT(PROXY, STRINGPOINT, 4), 
 
  ... 

-Greg

May I suggest running it through the C preprocessor (e.g. gcc -E) ? That produces the following output for me:

 typedef enum { 
 
 CURLOPT_ WRITEDATA = 10000 + 1, 
 
 CURLOPT_ URL = 10000 + 2, 
 
 CURLOPT_ PORT = 0 + 3, 
 
 CURLOPT_ PROXY = 10000 + 4, 
 
 CURLOPT_ USERPWD = 10000 + 5, 
 
 CURLOPT_ PROXYUSERPWD = 10000 + 6, 
 
 CURLOPT_ RANGE = 10000 + 7, 
 
 
 CURLOPT_ READDATA = 10000 + 9, 
 
 
 CURLOPT_ ERRORBUFFER = 10000 + 10, 
 
... 

You'll probably have to do this one per platform+arch combo, but the post-processed output is probably a lot easier to deal with...

(A shame that "gcc -E" doesn't have an option to process everything _except_ #include statements, though...)

new topic     » goto parent     » topic index » view message » categorize

5. Re: HTTPS with OpenSSL

jimcbrown said...

May I suggest running it through the C preprocessor (e.g. gcc -E) ? That produces the following output for me:

You'll probably have to do this one per platform+arch combo, but the post-processed output is probably a lot easier to deal with...

(A shame that "gcc -E" doesn't have an option to process everything _except_ #include statements, though...)

This is what I came up with. I translated all of the macro-mania with the Regex find/replace in Notepad++.

map m_CURLopttype = map:new_from_kvpairs({ 
    { "CURLOPTTYPE_LONG",          CURLOPTTYPE_LONG }, 
    { "CURLOPTTYPE_OBJECTPOINT",   CURLOPTTYPE_OBJECTPOINT }, 
    { "CURLOPTTYPE_STRINGPOINT",   CURLOPTTYPE_OBJECTPOINT }, 
    { "CURLOPTTYPE_FUNCTIONPOINT", CURLOPTTYPE_FUNCTIONPOINT }, 
    { "CURLOPTTYPE_OFF_T",         CURLOPTTYPE_OFF_T } 
}) 
 
constant LONG          = "CURLOPTTYPE_LONG" 
constant OBJECTPOINT   = "CURLOPTTYPE_OBJECTPOINT" 
constant STRINGPOINT   = "CURLOPTTYPE_OBJECTPOINT" 
constant FUNCTIONPOINT = "CURLOPTTYPE_FUNCTIONPOINT" 
constant OFF_T         = "CURLOPTTYPE_OFF_T" 
 
map m_CURLoption  = map:new() 
 
function CINIT( sequence na, sequence t, atom nu ) 
 
    atom value = map:get( m_CURLopttype, t, 0 ) + nu 
    map:put( m_CURLoption, "CURLOPT_" & na, {na,t,nu} ) 
 
    return value 
end function 
 
public constant 
 
    /* This is the FILE * or void * the regular output should be written to. */ 
    CURLOPT_WRITEDATA = CINIT("WRITEDATA", OBJECTPOINT, 1), 
 
    /* The full URL to get/put */ 
    CURLOPT_URL = CINIT("URL", STRINGPOINT, 2), 
 
    /* Port number to connect to, if other than default. */ 
    CURLOPT_PORT = CINIT("PORT", LONG, 3), 
 
    /* Name of proxy to use. */ 
    CURLOPT_PROXY = CINIT("PROXY", STRINGPOINT, 4), 
 
... 

Not sure how necessary this approach is when all I need are constant values... getlost

-Greg

new topic     » goto parent     » topic index » view message » categorize

6. Re: HTTPS with OpenSSL

ghaberek said...

Not sure how necessary this approach is when all I need are constant values... getlost

I agree. I prefered to skip all unneeded code.

enum type CURLoption 
  CURLOPT_WRITEDATA       = 10001,  /* This is the FILE * or void * the regular output should be written to. */ 
  CURLOPT_URL             = 10002,  /* The full URL to get/put */ 
  CURLOPT_PORT            =     3,  /* Port number to connect to, if other than default. */ 
  CURLOPT_PROXY           = 10004,  /* Name of proxy to use. */ 
  CURLOPT_USERPWD         = 10005,  /* "user:password;options" to use when fetching. */ 
  CURLOPT_PROXYUSERPWD    = 10006,  /* "user:password" to use with proxy. */ 
  CURLOPT_RANGE           = 10007,  /* Range to get, specified as an ASCII string. */ 
  /* not used */ 
  CURLOPT_READDATA        = 10009,  /* Specified file stream to upload from (use as input): */ 
  CURLOPT_ERRORBUFFER     = 10010,  /* Buffer to receive error messages in, must be at least CURL_ERROR_SIZE 

                                     * bytes big. If this is not used, error messages go to stderr instead: */ 
  CURLOPT_WRITEFUNCTION   = 20011,  /* Function that will be called to store the output (instead of fwrite). The 

                                     * parameters will use fwrite() syntax, make sure to follow them. */ 
  CURLOPT_READFUNCTION    = 20012,  /* Function that will be called to read the input (instead of fread). The 

                                     * parameters will use fread() syntax, make sure to follow them. */ 

Jean-Marc

new topic     » goto parent     » topic index » view message » categorize

7. Re: HTTPS with OpenSSL

ghaberek said...

It would probably be better to wrap the shared libraries instead of calling out to an external process.

I would also recommend using libcurl instead of calling OpenSSL directly. I'm surprised we don't have a better wrapper in The Archive.

I'll see if that's something I can knock out quickly. Their page on Euphoria also needs updating since Ray Smith left our community long ago.

-Greg

Maybe using an external DLL is a better way than calling an external process, but I built a libcurl wrapper from the 7.50.1 release and it runs short: it exits without any error on curl_global_init(CURL_GLOBAL_DEFAULT) which has to be the first call.

Jean-Marc

new topic     » goto parent     » topic index » view message » categorize

8. Re: HTTPS with OpenSSL

jmduro said...

Maybe using an external DLL is a better way than calling an external process, but I built a libcurl wrapper from the 7.50.1 release and it runs short: it exits without any error on curl_global_init(CURL_GLOBAL_DEFAULT) which has to be the first call.

Did you declare the external functions with a '+' to indicate CDECL calling convention? (See 8.39.4.4 define_c_func)

constant x_curl_global_init = define_c_func( libcurl, "+curl_global_init", {C_LONG}, C_INT ) 
 
public function curl_global_init( atom flags ) 
    return c_func( x_curl_global_init, {flags} ) 
end function 

-Greg

new topic     » goto parent     » topic index » view message » categorize

9. Re: HTTPS with OpenSSL

Thanks a lot Greg!

No I didn't prefix function names with a plus. Now the first example works. I cant get the page content of https://example.com.

I have to transcript many more C examples before I can provide the source code. Some functions have not been ported. Only a subset of the easy branch has been ported.

Jean-Marc

new topic     » goto parent     » topic index » view message » categorize

10. Re: HTTPS with OpenSSL

This is much to complicated for me. I have only been able to make 2 examples work. Once callbacks are needed, I can't get examples work.

Source code is here: http://jean-marc.duro.pagesperso-orange.fr/libcurl_v0.0.2.zip

Jean-Marc


Forked into: libcurl - help needed

new topic     » goto parent     » topic index » view message » categorize

11. Re: HTTPS with OpenSSL

I tried another approach by adding missing functions to Fabio Ramirez's wrapper for Wininet.

Source code is here: http://jean-marc.duro.pagesperso-orange.fr/EInetLib_full.ew

I ran following code:

include win32lib.ew 
include EInetLib_full.ew 
 
function sslInet(sequence AServer, sequence AUrl, sequence AData, integer blnSSL) 
  sequence aBuffer, s 
  sequence Header 
  sequence sMethod 
  integer BytesRead 
  atom pSession, pConnection, pRequest 
  integer res 
 
  sequence Result 
 
  Result = "" 
 
  pSession = InternetOpen({}) 
  if pSession > 0 then 
    if blnSSL then 
      pConnection = InternetConnect({ 
                      {"session_id" , pSession}, 
                      {"server_name", AServer}, 
                      {"server_port", INTERNET_DEFAULT_HTTPS_PORT}, 
                      {"user_name"  , NULL}, 
                      {"password"   , NULL}, 
                      {"service"    , "HTTP"} 
                    }) 
    else 
      pConnection = InternetConnect({ 
                      {"session_id" , pSession}, 
                      {"server_name", AServer}, 
                      {"server_port", INTERNET_DEFAULT_HTTP_PORT}, 
                      {"user_name"  , NULL}, 
                      {"password"   , NULL}, 
                      {"service"    , "HTTP"} 
                    }) 
    end if 
 
    if pConnection > 0 then 
      if length(AData) then 
        sMethod = "POST" 
      else 
        sMethod = "GET" 
      end if 
       
      if blnSSL then 
        pRequest = HttpOpenRequest({ 
                     {"connection_id", pConnection}, 
                     {"method", sMethod}, 
                     {"object_name", AUrl}, 
                     {"version", NULL}, 
                     {"referrer", NULL}, 
                     {"extra_headers", NULL}, 
                     {"flags", or_bits(INTERNET_FLAG_SECURE, INTERNET_FLAG_KEEP_CONNECTION)} 
                   }) 
      else 
        pRequest = HttpOpenRequest({ 
                     {"connection_id", pConnection}, 
                     {"method", sMethod}, 
                     {"object_name", AUrl}, 
                     {"version", NULL}, 
                     {"referrer", NULL}, 
                     {"extra_headers", NULL}, 
                     {"flags", INTERNET_SERVICE_HTTP} 
                   }) 
      end if 
 
      if pRequest > 0 then 
        Header = "Host: " & AServer & 10 & 
          "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10" & 10 & 
          "Accept: text/html,application/xhtml&xml,application/xml;q=0.9,*/*;q=0.8" & 10 & 
          "Accept-Language: en-us,en;q=0.5" & 10 & 
          "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7" & 10 & 
          "Keep-Alive: 300" & 10 & 
          "Connection: keep-alive"& 10 & 10 
 
        res = HttpAddRequestHeaders({ 
          {"request_id", pRequest}, 
          {"headers", Header}, 
          {"headers_length", length(Header)}, 
          {"modifiers", HTTP_ADDREQ_FLAG_ADD} 
        }) 
 
        if HttpSendRequest({ 
          {"request_id", pRequest}, 
          {"headers", NULL}, 
          {"headers_length", 0}, 
          {"optional"  , AData}, 
          {"optional_length", length(AData)} 
        }) then 
 
          aBuffer = "" 
          s = InternetReadFile(pRequest, 4096) 
          while length(s) do 
            aBuffer &= s 
            s = InternetReadFile(pRequest, 4096) 
          end while 
        end if 
 
        res = InternetCloseHandle(pRequest) 
      end if 
 
      res = InternetCloseHandle(pConnection) 
    end if 
 
    res = InternetCloseHandle(pSession) 
  else 
    Msg(net_err_msg) 
  end if 
  return aBuffer 
end function 
 
puts(1, sslInet("geneanet.org", "https://gw.geneanet.org/connexion/", 
          "\"_username\": \"myUsername\", \"_password\": \"myPassword\", \"_submit\": \"\"", 1)) 

Added functions seem to work well, but when I try to get a page with InternetReadFile or another similar function, I get an error HTTP 400 The plain HTTP request was sent to HTTPS port. That is supposed to happen when HttpOpenRequest is run without the INTERNET_FLAG_SECURE flag, but I use this flag so I don't understand what's happening.

Jean-Marc

new topic     » goto parent     » topic index » view message » categorize

12. Re: HTTPS with OpenSSL

I built my own wininet wrapper. Source code is here: http://jean-marc.duro.pagesperso-orange.fr/WININET.e

I ran following code:

include get.e 
include Wininet.e 
 
  sequence aBuffer, s 
  sequence Header 
  atom pSession, pConnection, pRequest 
  integer res, f_out 
 
  sequence Result 
 
  sequence AData 
 
  AData = "\"_username\": \"myUsername\", \"_password\": \"myPassword\", \"_submit\": \"\"" 
  Result = "" 
  f_out = open("geneanet.htm", "w") 
  pSession = InternetOpen("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0", 0, NULL, NULL, 0) 
  if pSession > 0 then 
    pConnection = InternetConnect(pSession, "geneanet.org", 
                                  INTERNET_DEFAULT_HTTPS_PORT, 
                                  NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0) 
 
    if pConnection > 0 then 
      pRequest = HttpOpenRequest(pConnection, "POST", "/connexion/", NULL, NULL, NULL, 
                                 or_bits(INTERNET_FLAG_SECURE, INTERNET_FLAG_KEEP_CONNECTION), 0) 
 
      if pRequest > 0 then 
        Header = "Host: geneanet.org" & 10 & 
          "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0" & 10 & 
          "Accept: text/html,application/xhtml&xml,application/xml;q=0.9,*/*;q=0.8" & 10 & 
          "Accept-Language: en-us,en;q=0.5" & 10 & 
          "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7" & 10 & 
          "Keep-Alive: 300" & 10 & 
          "Connection: keep-alive"& 10 & 10 
 
        res = HttpAddRequestHeaders(pRequest, Header, HTTP_ADDREQ_FLAG_ADD) 
 
        if HttpSendRequest(pRequest, NULL, AData) then 
 
          aBuffer = "" 
          s = InternetReadFile(pRequest, 4096) 
          while length(s) do 
            aBuffer &= s 
            s = InternetReadFile(pRequest, 4096) 
          end while 
        end if 
 
        res = InternetCloseHandle(pRequest) 
      else 
        puts(1, sys_FormatMessage(GetError()) & "\n") 
      end if 
 
      res = InternetCloseHandle(pConnection) 
    else 
      puts(1, sys_FormatMessage(GetError()) & "\n") 
    end if 
 
    res = InternetCloseHandle(pSession) 
  else 
    puts(1, sys_FormatMessage(GetError()) & "\n") 
  end if 
  puts(f_out, aBuffer) 
  close(f_out) 
  puts(1, "Press any key ...\n") 
  res = wait_key() 
 

Apart of authentication problems (Geneanet needs CSRF tokens), this seems to work.

Jean-Marc

new topic     » goto parent     » topic index » view message » categorize

Search



Quick Links

User menu

Not signed in.

Misc Menu