I have a question here, I try to transfer a variable to my PHP script in order to retrieve data from Bing Search API.
I use the following AJAX code:
var bingquery = 'bingquery=' + $('#query').val();
console.log(bingquery);
$.ajax({
method: "POST",
url: "hw8.php",
dataType: "json",
data: bingquery,
success: function(jsondata){
console.log('***Test for News Feeds***');
console.log(jsondata);
}
});
And my PHP is:
if (isset($_POST["bingquery"])){
// Replace this value with your account key
$accountKey = '***myaccountkey***';
$WebSearchURL = 'https://api.datamarket.azure.com/Bing/Search/v1/' + 'News?$format=json&Query=';
$cred = sprintf('Authorization: Basic %s', base64_encode($accountKey . ":" . $accountKey) );
$context = stream_context_create(array(
'http' => array(
'header' => $cred
)
));
$request = $WebSearchURL . urlencode( '\'' . $_POST["bingquery"] . '\'');
//if I hard code the request URL here, it does work.
$response = file_get_contents($request, 0, $context);
echo $response;
}
I wonder if there is something wrong with my URL encoding? Because the console says file_get_contents(0%27MYSYMBOL%27) fails, MYSYMBOL is the string I want in the search.
Thank you so much for your help!
There is nothing wrong about the encoding at all, urlencode is supposed to make the input string url safe, and that is exactly what it is doing, \ has special meaning in a url and hence it is being encoded by the function.
UPDATE
You are adding up two strings, in PHP . is used to concatenate two strings, make the following changes,
$WebSearchURL = 'https://api.datamarket.azure.com/Bing/Search/v1/News';
$request = $WebSearchURL .'?Query='.urlencode($_POST["bingquery"]).'&$format=json;
Related
I'm building a PHP based webpage. I want to use REST APIs to store, read and update data in an AZURE Cosmos DB.
First challenge is to generate the authentication token with the masterkey. I used the Microsoft documentation: https://learn.microsoft.com/de-de/rest/api/cosmos-db/access-control-on-cosmosdb-resources
and a postman collection* as a reference to build the code below.
*see
https://www.youtube.com/watch?v=2ndj_-zp82Y
https://github.com/MicrosoftCSA/documentdb-postman-collection
I used the console log from postman to compare each step and figured out that there are different results** but I have no clue how to get the same results from my PHP like from Postman
**
key64 PHP:
WUtDMFF5VWZkZ1NsTFp5UmU5ZVBMbW9jV3ZSN.....==
key64 Postman:
60a0b443251f7604a52d9c917bd78f2e6a1c5af4790d3f67dc7dbd513d173418... NO == at the end
authstring PHP:
type%253Dmaster%2526ver%253D1.0%2526sig%.....
authstring POSTMAN:
type%3Dmaster%26ver%3D1.0%26sig%3.....
POSTMAN (JS)
// store our master key for documentdb
var mastKey = postman.getEnvironmentVariable("DocumentDBMasterKey");
console.log("mastKey = " + mastKey);
// store our date as RFC1123 format for the request
var today = new Date();
var UTCstring = today.toUTCString();
postman.setEnvironmentVariable("RFC1123time", UTCstring);
// Grab the request url
var url = request.url.trim();
console.log("request url = " + url);
// strip the url of the hostname up and leading slash
var strippedurl = url.replace(new RegExp('^https?://[^/]+/'),'/');
console.log ("stripped Url = " + strippedurl);
// push the parts down into an array so we can determine if the call is on a specific item
// or if it is on a resource (odd would mean a resource, even would mean an item)
var strippedparts = strippedurl.split("/");
var truestrippedcount = (strippedparts.length - 1);
console.log(truestrippedcount);
// define resourceId/Type now so we can assign based on the amount of levels
var resourceId = "";
var resType = "";
// its odd (resource request)
if (truestrippedcount % 2)
{
console.log("odd");
// assign resource type to the last part we found.
resType = strippedparts[truestrippedcount];
console.log("resType");
console.log(resType);
if (truestrippedcount > 1)
{
// now pull out the resource id by searching for the last slash and substringing to it.
var lastPart = strippedurl.lastIndexOf("/");
resourceId = strippedurl.substring(1,lastPart);
console.log(resourceId);
}
}
else // its even (item request on resource)
{
console.log("even");
// assign resource type to the part before the last we found (last is resource id)
resType = strippedparts[truestrippedcount - 1];
console.log("resType");
// finally remove the leading slash which we used to find the resource if it was
// only one level deep.
strippedurl = strippedurl.substring(1);
console.log("strippedurl");
// assign our resourceId
resourceId = strippedurl;
console.log("resourceId");
console.log(resourceId);
}
// assign our verb
var verb = request.method.toLowerCase();
// assign our RFC 1123 date
var date = UTCstring.toLowerCase();
// parse our master key out as base64 encoding
var key = CryptoJS.enc.Base64.parse(mastKey);
console.log("key = " + key);
// build up the request text for the signature so can sign it along with the key
var text = (verb || "").toLowerCase() + "\n" +
(resType || "").toLowerCase() + "\n" +
(resourceId || "") + "\n" +
(date || "").toLowerCase() + "\n" +
"" + "\n";
console.log("text = " + text);
// create the signature from build up request text
var signature = CryptoJS.HmacSHA256(text, key);
console.log("sig = " + signature);
// back to base 64 bits
var base64Bits = CryptoJS.enc.Base64.stringify(signature);
console.log("base64bits = " + base64Bits);
// format our authentication token and URI encode it.
var MasterToken = "master";
var TokenVersion = "1.0";
auth = encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + base64Bits);
console.log("auth = " + auth);
// set our auth token enviornmental variable.
postman.setEnvironmentVariable("authToken", auth);
PHP CODE
#PHP Script
function generateAuthKey($url, $method){
$key = "****************";
$date = new DateTime('');
$date = $date->format('D, d M Y H:i:s O');
$ressourcetype = "";
$strippedurl = parse_url($url, PHP_URL_PATH);
$strippedparts = explode("/", $strippedurl);
$strippedurlcount = sizeof($strippedparts)-1;
#GET RESSOURCE TYPE
if ($strippedurlcount % 2){
$resType = $strippedparts[$strippedurlcount];
if ($strippedurlcount > 1){
$ressourcetype = $strippedparts[$strippedurlcount];
}
}
else{
$ressourcetype = $strippedparts[$strippedurlcount-1];
}
$sig = nl2br(strtolower($method)."\n".strtolower($ressourcetype)."\n".$strippedurl."\n".strtolower($date)."\n".""."\n");
$sig = utf8_encode($sig);
$key64 = base64_encode($key);
echo $key64."\n";
$hmac = hash_hmac('sha256',$sig,$key64);
$token = "type=master&ver=1.0&sig=".$hmac;
return urlencode($token);
}
How can I change the PHP script to provide the same output as Postman (JS)?
I believe the issue is with the following line of code:
$key64 = base64_encode($key);
As per the REST API documentation, you should be doing a base64_decode of your key as the key is already base64 encoded.
Please try by changing your code to:
$key64 = base64_decode($key);
While this is an old question, I'll note a couple issues with the author's code:
It was not necessary to utf8_encode() -- trying to utf8 encode a string that is already valid ISO-8859-1 can produce unexpected results. Also note that this method is deprecated as of PHP 8.2.0.
The author was returning the string output of hash_hmac(), rather than binary.
Here is how you correctly generate a signature in PHP. Bear in mind that all requests to the Cosmos REST API, needs to include an x-ms-date header, which matches the same date used to generate the token. It's up to you how to want to handle that, but in my case, I chose to return the date and token as an array from the function. You could also consider making the function return the entire header array all at once.
I am using Carbon and Guzzle in this example.
private function cosmosAuth(string $method, string $resourceType, string $resourceLink)
{
$date = Carbon::now()->toRfc7231String();
// alternatively: gmdate('D, d M Y H:i:s T')
$key = base64_decode(MY_COSMOS_KEY);
$body = $method . "\n" .
$resourceType . "\n" .
$resourceLink . "\n" .
$date . "\n" .
"\n";
$hash = hash_hmac('sha256', strtolower($body), $key, true);
$signature = base64_encode($hash);
$tokenType = "master";
$tokenVersion = "1.0";
$token = urlencode("type={$tokenType}&ver={$tokenVersion}&sig={$signature}");
return [
'date' => $date,
'token' => $token
];
}
Here is an example of a request to update a document. Note if you created your collection with a partition key, you also must pass the partition key value in the x-ms-documentdb-partitionkey header. The format here is a little janky, with Microsoft expecting a string representation of an array containing the value.
$resource = "dbs/{$database}/colls/{$collection}/docs/{$documentId}";
$auth = $this->cosmosAuth("PUT", "docs", $resource);
try {
$client = new \GuzzleHttp\Client();
$client->request("PUT", "https://{$account}.documents.azure.com/{$resource}", [
'headers' => [
'authorization' => $auth['token'],
'x-ms-date' => $auth['date'],
'x-ms-documentdb-partitionkey' => '["'.$documentId.'"]',
],
'json' => $documentData
]);
}
catch (Exception $e) {
$this->log("error: {$e->getMessage()}");
}
Microsoft resources:
Constructing a hashed token
Replace a document
I have an AJAX call which runs on a form submit (with prevent default to stop standard submit):
var form = $(this);
$.ajax({
type: form.attr('method'),
url: form.attr('action'),
data: form.serialize()
}).done(function(data) {
$('#processingFile').hide();
$('#downloadFile').show();
$('#shareURL').val(data.url);
$('#downloadFile').attr('href', data.url);
$('#aboutFile').html('<b>File URL:</b> ' + data.url + '<br /><b>File Size:</b> ' + data.size + '<br /><b>Time Stamp:</b> ' + data.timestamp + '<br /><b>Client IP:</b> ' + data.ip);
}).fail(function() {
$('#saveFile').hide();
$('#error').show();
});
The file it submits to is a PHP file which is as follows:
// VARIABLES
$fileURL = $_POST['fileURL'];
$tmpURL = substr(md5(rand()), 0, 7);
$deleteCode = md5($tmpURL);
// COOKIE
setcookie($tmpURL, $deleteCode, time()+86400);
// SAVE FILE
if($fileURL){
file_put_contents("tmp/" . $tmpFile, file_get_contents("http://" . $fileURL));
}
// OUTPUT
$result = array(
'url' => "tmp/" . $tmpFile,
'size' => filesize("tmp/" . $tmpFile) * .0009765625 * .0009765625,
'timestamp' => date('H:i:s d-m-Y'),
'ip' => $_SERVER['REMOTE_ADDR']
);
echo json_encode($result);
When the script is run everywhere which uses data.x in the jQuery returns undefined. Any idea why that happens and how to fix it?
data is a string containing your returned JSON text; it isn't an object.
To parse the JSON object, you have a couple of options:
Call JSON.parse() yourself.
Pass dataType: "json" to tell jQuery AJAX to parse it for you.
Set Content-Type: application/json in the server's response so that jQuery knows to parse it for you.
Set dataType: 'json' and check! Look this doc and set your datType.
I'm trying to upload the base64 data of an image directly through javascript to Google Storage using signed URLs as authentication, which is apparently possible to do.
According to developers.google.com/storage/docs/reference-methods#putobject there are only six headers that need to be set for this to work. Also for the header 'Authorization' I'm attempting to use the last option here:
developers.google.com/storage/docs/reference-headers#authorization
Which is 'A signature' developers.google.com/storage/docs/authentication#service_accounts
The only thing I want to use PHP for is to get the signature. Here is what I have been trying to get working with no success.
PHP & JS page/code
<?php
$theDate = Date(DATE_RFC822);
function signedURL( $filename, $bucket, $method = 'PUT' ) {
$signature = "";
$duration = 30;
$emailID = "980000000000-ytyertyr#developer.gserviceaccount.com";
$certs = array();
$priv_key = file_get_contents("9999999999999999999999999999-privatekey.p12");
if (!openssl_pkcs12_read($priv_key, $certs, 'notasecret')) { echo "Unable to parse the p12 file. OpenSSL error: " . openssl_error_string(); exit(); }
$expires = time() + $duration;
$to_sign = ( $method . "\n\n\n" . $expires . "\n" . "/" . $bucket . "/" . $filename );
$RSAPrivateKey = openssl_pkey_get_private($certs["pkey"]);
if (!openssl_sign( $to_sign, $signature, $RSAPrivateKey, 'sha256' ))
{
error_log( 'openssl_sign failed!' );
$signature = 'failed';
} else {
$signature = urlencode( base64_encode( $signature ) );
}
return (
'http://storage.googleapis.com/' . $bucket . '/' . $filename . '?GoogleAccessId=' . $emailID . '&Expires=' . $expires . '&Signature=' . $signature
);
openssl_free_key($RSAPrivateKey);
}
?>
<script>
var base64img = 'data:image/png;base64,AAABAAIAICA....snip...A';
var xhr = new XMLHttpRequest();
//PUT test - PUT status "(Canceled)" - OPTION status 200 (OK)
xhr.open("PUT", "<?php echo signedURL('test.png', 'mybucket'); ?>");
//xhr.setRequestHeader("Content-type", "image/png");
xhr.setRequestHeader("x-goog-acl", "public-read"); //try to set public read on file
xhr.setRequestHeader("Content-Length", base64img.length); // Chrome throws error (Refused to set unsafe header "Content-Length" )
xhr.send( base64img );
//GET test.txt temp file - working and returning 200 status (signing must be working ?)
/*
xhr.open("GET", "<?php echo signedURL('test.txt', 'mybucket', 'GET'); ?>");
xhr.send();
*/
//
</script>
Cors xml (seems to be fine) - I've set a wildcard only while testing and a low cache/maxage time
<?xml version="1.0" ?>
<CorsConfig>
<Cors>
<Origins>
<Origin>*</Origin>
</Origins>
<Methods>
<Method>GET</Method>
<Method>HEAD</Method>
<Method>OPTIONS</Method>
<Method>PUT</Method>
</Methods>
<ResponseHeaders>
<ResponseHeader>accept-encoding</ResponseHeader>
<ResponseHeader>cache-control</ResponseHeader>
<ResponseHeader>content-length</ResponseHeader>
<ResponseHeader>content-type</ResponseHeader>
<ResponseHeader>expect</ResponseHeader>
<ResponseHeader>if-modified-since</ResponseHeader>
<ResponseHeader>origin</ResponseHeader>
<ResponseHeader>range</ResponseHeader>
<ResponseHeader>referer</ResponseHeader>
<ResponseHeader>x-goog-acl</ResponseHeader>
<ResponseHeader>x-goog-api-version</ResponseHeader>
</ResponseHeaders>
<MaxAgeSec>900</MaxAgeSec>
</Cors>
</CorsConfig>
I've tested the GET method on a file and get a 200 status back now (\n\n - fix)
Update:
Looking in Firefox it does return a 403, unlike Chrome.
So the following lines are weird, as the conflate signed URLs with OAuth and PUT with POST:
# This looks like a PUT to signed URL
xhr.open("PUT", '<?php echo signedURL('imgfile.png','PUT',30,'mybucketname'); ?>', true);
# But multipart requires POST
xhr.setRequestHeader("Content-type", "multipart/form-data; boundary="+boundary);
# And here's a second form of authorization
xhr.setRequestHeader("Authorization", "OAuth <?php echo $signature; ?>");
multipart/form-data uploads require POST verb and are intended for html forms: Google Cloud Storage : PUT Object vs POST Object to upload file.?.
As long as you are sending a custom headers in an XMLHttpRequest I would recommend using PUT with either OAuth credentials:
xhr.open("PUT", "https://storage.googleapis.com/mybucketname/imgfile.png");
xhr.setRequestHeader("Authorization", "OAuth Bearer 1234567abcdefg");
xhr.setRequestHeader("Content-Length", raw_img_bytes.length);
xhr.send(raw_img_bytes);
or a signed url:
xhr.open("PUT", "https://storage.googleapis.com/mybucketname/imgfile.png?" +
"GoogleAccessId=1234567890123#developer.gserviceaccount.com&" +
"Expires=136891473&" +
"Signature=BClz9e...WvPcwN%2BmWBPqwg...sQI8IQi1493mw%3D");
xhr.setRequestHeader("Content-Length", raw_img_bytes.length);
xhr.send(raw_img_bytres);
I gess your Content-Type is something known (like Content-Type:video/mp4 for instance)? Try to upload a file with not known extention. For me, PUT is working in this case, not when Content-Type is not empty...
I don't understand why...
I am trying to work with the PHP API for Amazon's Flexible Payments.
Here is my PHP snippet to send a payment request:
<?php
$string_to_sign = 'GET
authorize.payments-sandbox.amazon.com
/cobranded-ui/actions/start
SignatureMethod=HmacSHA256&SignatureVersion=2&callerKey=my_access_key&callerReference=YourCallerReference&paymentReason=donation&pipelineName=SingleUse&returnUrl=http%3A%2F%2Fproblemio.com&transactionAmount=4.0';
$encoded_string_to_sign = URLEncode(Base64_Encode(hash_hmac('sha256', $string_to_sign, 'my_secret_key')));
$amazon_request_sandbox = 'https://authorize.payments-sandbox.amazon.com/cobranded-ui/actions/start?SignatureVersion=2&returnUrl='.$return_url.'&paymentReason='.$payment_reason.'&callerReference=YourCallerReference&callerKey='.$my_access_key_id.'&transactionAmount=4.0&pipelineName=SingleUse&SignatureMethod=HmacSHA256&Signature='.$encoded_string_to_sign;
// When it goes to the url, it gets the invalid signature error
header('Location: '.$amazon_request_sandbox);
?>
This seems to be following their instructions, but I can't get past that error.
Thanks!!
<?php
$method = 'GET';
$host = 'authorize.payments-sandbox.amazon.com';
$path = '/cobranded-ui/actions/start';
$params = array(
'signatureMethod' => 'HmacSHA256',
'signatureVersion' => '2',
'currencyCode' => 'USD',
'callerKey' => 'Your_Key_ID',
'callerReference' => 'YourCallerReference',
'paymentReason' => 'donation',
'pipelineName' => 'SingleUse',
'returnUrl' => 'http://yourcallback.com',
'transactionAmount'=> '5',
'version' => '2009-01-09',
);
$params = array_map('rawurlencode', $params);
$paramStringArray = array();
foreach($params as $key => $value){
$paramStringArray[] = $key . '=' . $value;
}
$paramString = implode('&', $paramStringArray);
$string_to_sign = $method . "\n"
. $host . "\n"
. $path . "\n"
. $paramString;
$signature = base64_encode(hash_hmac(
'sha256',
$string_to_sign,
'Your_Super_Secret_Key',
true
));
$amazon_request_sandbox = "https://{$host}{$path}?" . $paramString .
'&signature=' . rawurlencode($signature);
header('Location: '.$amazon_request_sandbox);
?>
Okay... using the structure from the code below, I've finally figured this whole thing out via the code above. There are three things of note to keep track of while forming your signature/URL...
It seems that the parameter "transactionAmount" is necessary for a valid Co-branded UI Pipeline, even though there's no specific instruction alluding to the issue.
If any of your parameters have/had spaces in them, and you tried to use html_build_query() in all but the latest (5.4) version of PHP, you would be given an encoding scheme that featured "+" marks for spaces instead of "%20" which is what Amazon appears to like. My code above takes care of that by implementing rawurlencode() on the entire parameter array.
The ordering of the parameters is paramount in the construction of the signature. The keys (not the values) need to be in case-insensitive alphabetical order. It's also worth noting that despite what the documentation says for the API, both the ampersands (&) and the equals (=) must be present in the creation of the query string for the signature.
Ex:
Query String for Signature: callerKey=1111111111111¤cyCode=USD&signatureVersion=2
Some Other Things I Noticed...
In the sample code included with the PHP SDK (2010-8-28), the "paymentReason" attribute in the file "CBUISingleUsePipelineSample.php" is listed as "HarryPotter 1-5 DVD set". Since this attribute has spaces in it, it throws that ever-annoying "invalid signature" error when you try to visit the generated link because html_build_query() is used to generate the query string for the URL. To fix this issue, open up "CBUIPipeline.php", and look for the following line in the constructUrl() method...
$queryString = http_build_query($parameters, '', '&');
replace it with:
$queryString = str_replace('+', '%20', http_build_query($parameters, '', '&'));
That'll solve the space-encoding problem for older versions of PHP (< 5.4). With the latest version, there's an "enc_type" flag you can set.
Last things Last...
This is my first post on StackOverflow so don't kill me if I broke protocol. Hope it helps!
Try this piece of code:
<?php
$method = 'GET';
$host = 'authorize.payments-sandbox.amazon.com';
$path = '/cobranded-ui/actions/start';
$params = array(
'SignatureMethod' => 'HmacSHA256'
'SignatureVersion' => 2,
'callerKey' => 'my_access_key',
'callerReference' => 'YourCallerReference',
'paymentReason' => 'donation',
'pipelineName' => 'SingleUse',
'returnUrl' => 'http://problemio.com&transactionAmount=4.0',
);
$string_to_sign = $method . "\n"
. $host . "\n"
. $path . "\n"
. http_build_query($params);
$signature = base64_encode(hash_hmac(
'sha256',
$string_to_sign,
'my_secret_key'
));
$params['Signature'] = $signature;
$amazon_request_sandbox = "https://{$host}{$path}?" . http_build_query($params);
header('Location: ' . $amazon_request_sandbox);
So I made a few changes:
PHP's http_build_query() to build the query string (ensure correct encoding)
trying to re-use your vars vs. duplicating the efforts (makes it easier to spot mistakes, etc.)
explicit \n - maybe your editor entered \r or \r\n
HTH
I am using Graph API https://developers.facebook.com/docs/reference/api/#publishing with PHP SDK and I would like to send some data via HTTP POST method, as it's mentioned in documentation (e.g. add comment).
At https://developers.facebook.com/docs/reference/api/batch/ they say I should encode the body of HTTP POST request as ...should be formatted as a raw HTTP POST body string, similar to a URL query string. I can't get the combination of PHP functions to get this to work. In the example they claim following should work:
"body": "message=Test status update"
Well, that works. But what if I need to add other params? And how should be this string encoded? E.g. I have this:
$data = array('name' => 'Gargamel', 'occupation' => 'Freelancing Smurf Hunter');
How should I process it to get required format? Following does NOT work:
$batch = array();
$query = array(
'method' => 'POST',
'relative_url' => '/forrest/full/of/smurfs',
'body' => urldecode(http_build_query($data)),
);
$batch[] = $query;
$responses = $this->api('/?batch=' . json_encode($batch, JSON_HEX_AMP), 'POST');
I explored half of the Internet, but I can't find any more specific information about the format than the one mentioned above (raw HTTP POST similar to a URL query string).
Thanks for any suggestions!
using this: http://forum.developers.facebook.net/viewtopic.php?pid=331343#p331343
$batch_array[] = array(
'method' => 'POST',
'relative_url' => 'Relative url',
'body' => 'message=' . 'Your message' . '%26data=' . 'Your data' ,
);
This is my batch example code:
$graph_url = "https://graph.facebook.com/me/friends?access_token=" . $params['access_token'];
$friends = json_decode(file_get_contents($graph_url));
$batched_request = '[{"method":"GET","relative_url":"'.$friends->data[0]->id.'/likes"}';
for ($i = 1; $i < 20; $i++) {
$batched_request .= ',{"method":"GET","relative_url":"'.$friends->data[$i]->id.'/likes"}';
}
$batched_request .= ']';
$post_url = 'https://graph.facebook.com/?batch='
. $batched_request . '&access_token=' . $params['access_token'] . '&method=post';
$posts = file_get_contents($post_url);
for ($i = 0; $i < 20; $i++) {
$post = json_decode($posts[$i]->body);
echo($friends->data[$i]->id.' '.$friends->data[$i]->name);
//print_r($post);
}
Batch query limit 20 requests. You need to decode each part of batch query, see code above.