I have some PHP code which queries a MySQL database for a count.
When queried via a browser I get the following output:
{"count":"123"}
I also have a Ruby script which executes the same PHP script via Net::HTTP but the output is different:
{"count"=>"123"}
Why is this?
//The URL
uri = URI.parse("http://lab/count.php")
http = Net::HTTP.new(uri.host, uri.port)
//Request URL
request = Net::HTTP::Get.new(uri.request_uri)
//Basic authentication
request.basic_auth("user1", "secret")
response = http.request(request)
//Response
response = JSON.parse(response.body)
puts results
//Value 'count'
count = JSON.parse(response.body)[0]
puts count
Thanks.
{"count"=>"123"} is not JSON response.
It's ruby literal for Hash table.
I think you are seeing the result of parsed JSON:
>> require 'json'
>> JSON.parse('{"count":"123"}') # => {"count"=>"123"}
>> puts JSON.dump({"count"=>"123"}) # prints => {"count":"123"}
UPDATE response to comment
To get 123 printed.
uri = URI.parse("http://lab/count.php")
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.request_uri)
request.basic_auth("user1", "secret")
response = http.request(request)
response = JSON.parse(response.body)
puts response['count']
Related
I have been working on a way to implement HMAC verification in python with flask for the selly.gg merchant website.
So selly's dev documentation give these following examples to verify HMAC signatures (in PHP and ruby): https://developer.selly.gg/?php#signing-validating
(code below:)
PHP:
<?php
$signature = hash_hmac('sha512', json_encode($_POST), $secret);
if hash_equals($signature, $signatureFromHeader) {
// Webhook is valid
}
?>
RUBY:
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha512'), secret, payload.to_json)
is_valid_signature = ActiveSupport::SecurityUtils.secure_compare(request.headers['X-Selly-Signature'], signature)
So, so far what I could figure out: They don't encode with base64 (like shopify and others do), it uses SHA-512, it encodes the secret code alongside json response data and finally the request header is 'X-Selly-Signature'
I've made the following code so far (based on shopify's code for HMAC signing https://help.shopify.com/en/api/getting-started/webhooks):
SECRET = "secretkeyhere"
def verify_webhook(data, hmac_header):
digest = hmac.new(bytes(SECRET, 'ascii'), bytes(json.dumps(data), 'utf8'), hashlib.sha512).hexdigest()
return hmac.compare_digest(digest, hmac_header)
try:
responsebody = request.json #line:22
status = responsebody['status']#line:25
except Exception as e:
print(e)
return not_found()
print("X Selly sign: " + request.headers.get('X-Selly-Signature'))
verified = verify_webhook(responsebody, request.headers.get('X-Selly-Signature'))
print(verified)
However selly has a webhook simulator, and even with the proper secret key and valid requests, the verify_webhook will always return False. I tried contacting Selly support, but they couldn't help me more than that
You can test the webhook simulator at the following address:
https://selly.io/dashboard/{your account}/developer/webhook/simulate
You're nearly right except that you don't need to json.dumps the request data. This will likely introduce changes into output, such as changes to formatting, that won't match the original data meaning the HMAC will fail.
E.g.
{"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"}
is different to:
{
"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"
}
which is actually:
{x0ax20x20"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"x0a}
A hash will be completely different for the two inputs.
See how json.loads and json.dumps will modify the formatting and therefore the hash:
http_data = b'''{
"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"
}
'''
print(http_data)
h = hashlib.sha512(http_data).hexdigest()
print(h)
py_dict = json.loads(http_data) # deserialise to Python dict
py_str = json.dumps(py_dict) # serialise to a Python str
py_bytes = json.dumps(py_dict).encode('utf-8') # encode to UTF-8 bytes
print(py_str)
h2 = hashlib.sha512(py_bytes).hexdigest()
print(h2)
Output:
b'{\n "id":"fd87d909-fbfc-466c-964a-5478d5bc066a"\n}\n'
364325098....
{"id": "fd87d909-fbfc-466c-964a-5478d5bc066a"}
9664f687a....
It doesn't help that Selly's PHP example shows something similar. In fact, the Selly PHP example is useless as the data won't be form encoded anyway, so the data won't be in $_POST!
Here's my little Flask example:
import hmac
import hashlib
from flask import Flask, request, Response
app = Flask(__name__)
php_hash = "01e5335ed340ef3f211903f6c8b0e4ae34c585664da51066137a2a8aa02c2b90ca13da28622aa3948b9734eff65b13a099dd69f49203bc2d7ae60ebee9f5d858"
secret = "1234ABC".encode("ascii") # returns a byte object
#app.route("/", methods=['POST', 'GET'])
def selly():
request_data = request.data # returns a byte object
hm = hmac.new(secret, request_data, hashlib.sha512)
sig = hm.hexdigest()
resp = f"""req: {request_data}
sig: {sig}
match: {sig==php_hash}"""
return Response(resp, mimetype='text/plain')
app.run(debug=True)
Note the use of request.data to get the raw byte input and the simple use of encode on the secret str to get the encoded bytes (instead of using the verbose bytes() instantiation).
This can be tested with:
curl -X "POST" "http://localhost:5000/" \
-H 'Content-Type: text/plain; charset=utf-8' \
-d "{\"id\":\"fd87d909-fbfc-466c-964a-5478d5bc066a\"}"
I also created a bit of PHP to validate both languages create the same result:
<?php
header('Content-Type: text/plain');
$post = file_get_contents('php://input');
print $post;
$signature = hash_hmac('sha512', $post, "1234ABC");
print $signature;
?>
I am trying to send a post request using Guzzle 6 http client. I am sending two requests one with content type as application/x-www-form-urlencoded (form_params in Guzzle) and the other as application/json (json in Guzzle).
I initialise the client as below (forms_params and json respectively):
$data1 = array("c1" => "a", "c2" => null)
$client = new Client();
$response = $client->post(
"http://localhost/callback",
array(
"form_params" => $data1, // send as x-www-form-urlencoded
)
);
$data2 = array("c1" => "a", "c2" => null)
$client = new Client();
$response = $client->post(
"http://localhost/callback",
array(
"json" => $data2, // send as json
)
);
The response that I receive does have not identical data/body:
Output for form_params : Data -> {"c1":"a"}
Output for json : Data -> {"c1":"a","c2":null}
I am not understanding why it does not send identical data for above requests. Could this be a bug in Guzzle? Is there any way to solve this (apart from removing nulls before sending request)?
UPDATE : As requested endpoint code (both requests are read using same code)
if ($$_SERVER["CONTENT_TYPE"] == "application/json") {
$jsonstr = file_get_contents("php://input");
$formData = json_decode($jsonstr, true);
} else {
$formData = $_POST;
}
echo "Data -> " . json_encode($formData);
UPDATE 2 : I went through the links provided in comments about this being expected behaviour in Guzzle.
But why I asked this question in first place is because I faced an issue of signature mismatch.
When I send the request, I add a header with a signature which is nothing but hash_hmac("sha256", json_encode($data), "secret_key"). So I get different signatures when sending data as json and form_params (since the data received is different in case of form_params as null values are discarded/not sent). First, I thought it might be because of a bug in Guzzle but it isn't.
Is there anyway to solve this signature issue?
As Jon Stirling and Lawrence Cherone noticed already, it's not a bug according to Guzzle's authors.
So the solution for you is to cast values to a string for form_params. It makes sense, because URL encoded format (unlike JSON) doesn't have types (all is a string). And you everyone defines own conversion rules. In PHP (using http_build_query) it works like this, skipping nulls at all.
I am sending the json data from a python program using the below code
import json
import requests
data = {"temp_value":132}
data_json = json.dumps(data)
payload = {'json_playload': data_json}
r = requests.get('http://localhost/json1/js2.php',data=payload)
and receiving it in a php server side using the below code.
<?php
if($_GET['temp_value']) {
$temp_value = $_GET['temp_value'];
echo $temp_value;
# $data = array($id, $description);
$arr=json_decode($temp_value,true);
} else {
echo "not found";}
// Connect to MySQL
include("dbconnect.php");
// Prepare the SQL statement
$SQL = "INSERT INTO test1.temperature1 (temp_value) VALUES ('$arr')";
// Execute SQL statement
mysqli_query($dbh,$SQL);
Echo "<a href=http://localhost/json1/review_data.php><center><u><b><h1>Manage values<h1><b><u></center></a>"
?>
along with the json data I have implemented like Id,time and date also gets updated in the database when i send the data.But what is happening here is like whenever i send the data from the python program it won't give any errors,when i see in the database and in the php page only 0(zero) values are inserted in both,however time and id gets updated.please someone suggest me a proper code and way.
Try this:
<?php
$json_payload = json_decode($_GET['json_payload']);
$temp_value = $json_payload['temp_value'];
?>
When you do this:
r = requests.get('http://localhost/json1/js2.php',data=payload)
You are sending a get request containing a JSON string representation of your data:
'{"temp_value": 132}'
stored in the get parameter named json_payload.
Therefore, on the client side, you must retrieve the contents of the json_payload parameter and decode the JSON string therein in order to retrieve temp_value.
Alternatively, you could do this:
payload = {"temp_value":132}
r = requests.get('http://localhost/json1/js2.php',data=payload)
And leave your PHP code unmodified.
import json
import requests
data = {"temp_value":132}
data_json = json.dumps(data)
payload = {'json_playload': data_json}
r = requests.get('http://localhost/json1/js2.php',data=payload)
print(r.url)//http://localhost/json1/js2.php
Json data is not passed in to php.Check your python code
payload = {'key1': 'value1', 'key2[]': ['value2', 'value3']}
r = requests.get("http://httpbin.org/get", params=payload)
print(r.url)
Result:
http://httpbin.org/get?key1=value1&key2%5B%5D=value2&key2%5B%5D=value3
You have to use like
payload = {"temp_value":132}
My rails application need to send some data to a php application, which expects a POST call.
I use the folowing code:
uri = URI.parse(apiUrl)
req = Net::HTTP::Post.new(uri.to_s, initheader = {'Content-Type' =>'application/json'})
req.basic_auth(api_key, token)
req.set_form_data({"action" => action, "data" => data})
http = Net::HTTP.new(uri.host, uri.port)
response = http.request(req)
Where data is a hash converted to json:
data = {
:key1 => val1,
:key2 => val2
}.to_json
(it is a nested hash, i.e. some values are hash as well)
My problem is that the php application receives 4 backslashes before each quotation mark:
$data_json = $_POST['data'];
error_log($data_json);
and in error log I see:
'{\\\\"key1\\\\":val1,\\\\"key2\\\\":\\\\"val2\\\\"}'
Looks like rails add one of them, but even if I remove it and replace it with the following code:
a.gsub!(/\"/, '\'')
I still get many backslashes inside the php application, hence cannot convert the string to array.
Any idea??
By using set_form_data net/http is POSTing the form as urlencoded. Its NOT posting your request body as pure JSON.
If you want to POST raw JSON you will need to follow a pattern like:
uri = URI('https://myapp.com/api/v1/resource')
req = Net::HTTP::Post.new(uri, initheader = {'Content-Type' =>'application/json'})
req.body = {param1: 'some value', param2: 'some other value'}.to_json
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(req)
end
I'm want to post a binary data string from a Python script to a webserver where a PHP script should pick it up and store it. I receive the whatever I echo in my POST part of the php script on the Python side so I assume, the actual POST works. However, I'm posting 23 Bytes and strlen($_POST['data']) stays 0.
My PHP script to pickj up the data looks like this:
if (isset($_REQUEST["f"]) && $_REQUEST["f"]=="post_status") {
$fname = "status/".time().".bin";
if (file_exists($fname)) {
$fname = $fname."_".rand();
if (file_exists($fname)) {
$fname = $fname."_".rand();
}
}
echo strlen($_POST['data'])." SUCCESS!";
}
and the Python POST script looks like this:
data = statusstr
host = HOST
func = "post_status"
url = "http://{0}{1}?f={2}".format(host,URI,func)
print url
r = urllib2.Request(url, data,{'Content-Type': 'application/octet-stream'})
r.get_method = lambda: 'PUT'
response = urllib2.urlopen(r)
print "RESPONSE " + response.read()
Why does my data not seem to get through, I'm wondering?
Thank you,
PHP will only populate posted values into the $_POST/REQUEST arrays for data that is sent as one of the form data content types. In your case, you need to read in the binary data directly from standard in like this:
$postdata = file_get_contents("php://input");