CURL POST Wordpress - wp_create_nonce problem - php

Hello everyone I have this CURL that works if I put the "wp_create_nonce" cookie user manually.
And when I execute the php from my browser with the user logged in.
The problem is to get the "wp_create_nonce" of the user from the cookies, is there any way to get it or simulate it?
The problem is when I run the php from the Cron it doesn't take the current session from the CURL to create the wp_create_nonce
Code CURL PHP
//We get wp_create_nonce from the current user logged
//$fb_product_id = 27540 is the ID of the product.
$nemo = wp_create_nonce( 'update-post_'.$fb_product_id);
$nemowoo = wp_create_nonce( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
//Start CURL
$chanelfacebook = curl_init();
curl_setopt($chanelfacebook, CURLOPT_URL, 'https://myserverexample.com/wp-admin/post.php');
curl_setopt($chanelfacebook, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($chanelfacebook, CURLOPT_POST, 1);
curl_setopt($chanelfacebook, CURLOPT_POSTFIELDS, "_wpnonce=".$nemo."&user_ID=1&action=editpost&post_ID=".$fb_product_id."&woocommerce_meta_nonce=".$nemowoo."&_wc_pre_orders_enabled=yes&facebookpreloadedu=yes&save=Actualizar");
curl_setopt($chanelfacebook, CURLOPT_ENCODING, 'gzip, deflate');
$headersfacebook = array();
$headersfacebook[] = 'Authority: miserverexample.com';
$headersfacebook[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9';
$headersfacebook[] = 'Accept-Language: es-419,es;q=0.9,en;q=0.8';
$headersfacebook[] = 'Cache-Control: max-age=0';
$headersfacebook[] = 'Content-Type: application/x-www-form-urlencoded';
$headersfacebook[] = 'Cookie: //cookie deleted for security//';
$headersfacebook[] = 'Origin: https://myserverexample.com';
$headersfacebook[] = 'Referer: https://myserverexample.com/wp-admin/post.php?post='.$fb_product_id.'&action=edit';
$headersfacebook[] = 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36';
curl_setopt($chanelfacebook, CURLOPT_HTTPHEADER, $headersfacebook);
$result = curl_exec($chanelfacebook);
if (curl_errno($chanelfacebook)) { error_log( print_r( "Error de Curl EN STOCK: ".curl_error($chanelfacebook), true ) ); }
curl_close($chanelfacebook);
The problem is creating the wp_create_nonce by doing it manually in the cron from the browser where the user is logged in works.
But if it is done from the cron when there is no user, it does not work.
How to solve it?
I can think of maybe an impersonate user to generate the code "wp_create_nonce"
P.D:
1.- I am doing CURL to the same origin server.
2.- CURL works fine if I run it from the browser where the curl cookie user is logged in.
In short I need to simulate the "wp_create_nonce" by user_id is this possible?
Example:
wp_create_nonce( 'update-post_'.$fb_product_id, $simulateiduser);

Related

Difference between command line cURL and PHP cURL

I have a cURL command like this:
curl 'https://www.example.com' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36' \
-H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3' \
-H 'accept-language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7' \
-H 'authority: www.example.com'
Executing this in a command line like in Terminal app on my Mac, results to the expected output.
(In case you test it yourself: If this output contains the word Sicherheitsüberprüfung it's geo blocked and you have to use a German IP to test it.)
I transferred the exact command to PHP cURL like this:
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
$headers = array();
$headers[] = 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36';
$headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3';
$headers[] = 'Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7';
$headers[] = 'Authority: www.example.com';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?>
When I run this code I'm getting a message that my request was recognized as automated request/robot: It says Sicherheitsüberprüfung, means security check.
Of course, I'm using the same IP for both, command line and PHP cURL request.
Why that? Isn't command line cURL the same as PHP cURL?
Or is there anything wrong with my PHP script?
UPDATE
I fortuitously found out the following: I'm using Coda as code editor on my Mac. This has a build-in PHP rendering engine. Using this with my PHP script, the result is as expected. It's the same result I'm getting in the command line.
UPDATE 2
I made what Jannes Botis suggested in his answer. I then ran the PHP script in my Coda code editor app (what output the expected) and with MAMP as localhost (what is always recognized as automated request).
I figured out that the the code executed with MAMP was using HTTP/2 while the code executed in Coda is using HTTP/1.1. To solve this, I added the following to the script:
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
Now, both output exact the same string:
GET / HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Authority: www.example.com
But, it's still the same: The one is working, the other is recognized as automated request.
Try to debug the request in both cases:
a) Terminal: use curl verbose mode: curl -v and check the http request sent, especially check the header list
b) php curl: print the http request using CURLINFO_HEADER_OUT:
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_exec($ch);
$info = curl_getinfo($ch);
print_r($info['request_header']);
Testing the different headers, what made it work was adding "Pragma: no-cache" header to the request:
$headers[] = 'Pragma: no-cache';
On the other hand, in terminal curl, I had to uppercase the request headers, e.g. User-Agent etc.
Try to create a tcp connection with fsockopen:
$fp = fsockopen("ssl://"."www.example.com", 443, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: www.example.com\r\n";
$headers = array();
$headers[] = 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36';
$headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3';
$headers[] = 'Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7';
$headers[] = 'Authority: www.example.com';
$out .= $headers;
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 1024);
}
fclose($fp);
and test if this works. Maybe the issue is either that php curl adds some info to the http request or the problem is on the tcp connection level, some info added there.
References
cURL works from Terminal, but not from PHP
PHP cURL: modify/overwrite Connection header
Sending TCP Data with PHP
Command line curl :
It is a tool to transfer data to or from a server, using any of the supported protocols (HTTP, FTP, IMAP, POP3, SCP, SFTP, SMTP, TFTP, TELNET, LDAP or FILE). curl is powered by Libcurl. This tool is preferred for automation, since it is designed to work without user interaction. curl can transfer multiple file at once.
For more details for Command line curl
Syntax:
curl [options] [URL...]
Example:
curl http://site.{one, two, three}.com
PHP cURL
$ch = curl_init('http://example.com/wp-login.php');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
if($this->getRequestType() == 'POST')
{
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS,
array(
'user[name]' => 'Generic+Username',
'user[email]' => 'mahekpatel04#gmail.com'
);
);
}
$response = curl_exec($ch);
The issue is with ciphers selected by PHP's cURL by default.
Running curl command with -Ivs options allows us to see what ciphers it uses:
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:#STRENGTH
Setting them in PHP allows it to bypass this mysterious check:
curl_setopt($ch,
CURLOPT_SSL_CIPHER_LIST,
'ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:#STRENGTH'
);
Also, it seems that Host header and using HTTPv2 should be added:
$headers[] = 'Host: www.11880.com';
// ...
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);

file_get_content Return something only on certain account but not on some other

Hi So I am setting up a system where you enter an isntagram username and then the website get the informations about this account (username, profilepic, followers and following...)
So i am doing this with simple php code with file_get_content and fetching to get the id of the suer and then go to the info page with this url and the preset instagram info link.
$username = $_POST['username'];
$html =file_get_contents('https://instagram.com/'.$username);
$subData=substr($html, strpos($html, 'window._sharedData'), strpos($html,
'};'));
$userid=strstr($subData, '"id":"');
$userid=str_replace('"id":"', '', $userid);
$userid=strstr($userid, '"', true);
$userData =
file_get_contents('https://i.instagram.com/api/v1/users/'.$userid.'/
info/');
$userDecodedData=json_decode($userData);
session_start();
$username = $userDecodedData->user->username;
$profilepicurl = $userDecodedData->user->hd_profile_pic_url_info->url;
$followers = $userDecodedData->user->follower_count;
$following = $userDecodedData->user->following_count;
$bio = $userDecodedData->user->biography;
$_SESSION['scoreinsta'] = $followers - $following;
So this works just fine when type my instagram username or my friend's but not when I try with kylie jenner's username or Instagram's or Ariana Grande, i've tried with cristiano ronaldo account to see if instagram was blocking all the most famous people but it works with his account :/ I'm kinda lost...
file_get_contents(https://i.instagram.com/api/v1/users/12281817/info/): failed to open stream: HTTP request failed! HTTP/1.1 500 Internal Server Error in C:\wamp64\www\Fame\addinsta.php on line 11
(error message I get when I trie with Kylie Jenner).
This is the error message but what I dont understand is that you can try the url it gives and it works jsut fine (you can see the info in an array or whathever) but the error message says he cant access it.
Edit: I'm currently trying with every most followed accounts I can and it doesnt work with taylor swift also.
You lack cookies to be able to retrieve data from the url:
you can try (ex: for case me):
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://i.instagram.com/api/v1/users/12281817/info/');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
$headers = array();
$headers[] = 'Authority: i.instagram.com';
$headers[] = 'Pragma: no-cache';
$headers[] = 'Cache-Control: no-cache';
$headers[] = 'Upgrade-Insecure-Requests: 1';
$headers[] = 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36';
$headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3';
// $headers[] = 'Accept-Encoding: gzip, deflate, br';
$headers[] = 'Accept-Language: vi-VN,vi;q=0.9,fr-FR;q=0.8,fr;q=0.7,en-US;q=0.6,en;q=0.5';
$headers[] = 'Cookie: mid=XS3ghgALAAGXu10Eb58jOsW7SAEi; fbm_124024574287414=base_domain=.instagram.com; csrftoken=tNVs4niJr2fLiLh76dPPGJuFaMlihIEd; ds_user_id=3043596499; sessionid=3043596499%3ArcZihGFrIEdqkX%3A6; shbid=14335; shbts=1565197428.8656168; rur=FTW; urlgen=^^^{\"113.177.118.128\":';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error:' . curl_error($ch);
}
curl_close($ch);
var_dump($result);

Trying to make a Roblox group payout API; doesn't pay out or give output

I am working on a Roblox group payout API, and if it works I am planning to set it open for public
Problem: It shows output {}, but it doesn't payout anything
Before I could start working on this, I first needed to create a manual payout where I got all the POST parameters and headers. Here is what I got:
METHOD: POST
URL: https://web.roblox.com/groups/3182156/one-time-payout/false
REQUEST BODY: percentages=%7B%22457792390%22:%221%22%7D
HEADERS:
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
referer: https://web.roblox.com/my/groupadmin.aspx?gid=3182156&_=1528631875891
cookie: GuestData=UserID=-608861174; RBXMarketing=FirstHomePageVisit=1; RBXSource=rbx_acquisition_time=6/9/2018 6:18:42 AM&rbx_acquisition_referrer=https://v3rmillion.net/showthread.php?tid=583440&rbx_medium=Direct&rbx_source=v3rmillion.net&rbx_campaign=&rbx_adgroup=&rbx_keyword=&rbx_matchtype=&rbx_send_info=1; rbx-ip=; __utmc=200924205; __utmz=200924205.1528621282.6.4.utmcsr=robuxrewards.site|utmccn=(referral)|utmcmd=referral|utmcct=/; __utma=200924205.428322191.1519910430.1528621282.1528630905.7; RBXImageCache=timg=63313634633937632D393938342D346262642D613663612D333133653130363363373938253231372E3130332E32392E32303925362F31302F323031382031313A34333A303220414D3E2434B19B5881BB5B51486D88F43FC8F5D5787F; __utmt_b=1; gig_hasGmid=ver2; .ROBLOSECURITY=HERE_WAS_A_COOKIE; RBXEventTrackerV2=CreateDate=6/10/2018 6:52:37 AM&rbxid=455629576&browserid=15138233029; __RequestVerificationToken=w6L7tvgTk0c8TeMvuz8QnvVEoF7W7mMxk6UcefoCygoXk97mWkqQGKiLD6XLz5Bssx9FTqkFCzvclhqdrVyww9VcrNY1; RBXSessionTracker=sessionid=a45dce07-ff59-4590-8881-b4200425cf02; __utmb=200924205.11.10.1528630905
I deleted the .ROBLOSECURITY because with that you can login into my account. But that is all the info I got. With the request body: percentages=%7B%22457792390%22:%221%22%7D, When I decode that, I get this: percentages={"457792390":"1"} That is good, because my user id is 457792390 and the amount I payed out is 1. So I created a code that should make this work, and make it automatic. Here it is:
<?php
// Receive
$module = $_GET['module'];
$cookie = $_GET['cookie'];
$amount = $_GET['amount'];
$group_id = $_GET['group_id'];
$user_id = $_GET['user_id'];
/* https://freewebhost.fun/api.php?module=group_payout&cookie=YOUR_COOKIE_HERE&amount=YOUR_AMOUNT_HERE&group_id=YOUR_GROUP_ID_HERE&user_id=USERNAME_HERE */
// The function
function group_payout($cookie, $amount, $group_id, $user_id) {
// preset stuff
$content_type = "application/x-www-form-urlencoded; charset=UTF-8";
// further
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,"https://web.roblox.com/groups/".$group_id."/one-time-payout/false");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "percentages=%7B%22" . $user_id . "%22:%22" . $amount . "%22%7D");
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36");
curl_setopt($ch, CURLOPT_HTTPHEADER, Array("Content-Type: ".$content_type, "Cookie: .ROBLOSECURITY=".$cookie."; RBXViralAsquisition=time=1/24/2018 11:50:50 AM&referrer=https://www.google.nl/&originatingsite=www.google.nl&viraltarget=945929481; RBXSource=rbx_acquisition_time=6/11/2018 1:47:00 AM&rbx_acquisition_referrer=&rbx_medium=Direct&rbx_source=&rbx_campaign=&rbx_adgroup=&rbx_keyword=&rbx_matchtype=&rbx_send_info=1; __utzm=200924205.1516985949.4.3.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); "));
curl_setopt($ch, CURLOPT_REFERER, 'https://web.roblox.com/my/groupadmin.aspx?gid='.$group_id.'#nav-payouts');
// Lets go
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec ($ch);
curl_close ($ch);
echo $server_output;
}
if ($module == "group_payout") {
group_payout($cookie, $amount, $group_id, $user_id);
}
?>
I really don't know what the problem can be.
Edit
So, in the comments somebody told me to try out PostMan. Here are the results:
https://pastebin.com/raw/iN4UQPBE (it's too big for the character limit here).
I don't know what to do with these results.
Your XSRF token is invalid. You should include it in the request headers.
To get your XSRF token, send a POST request to https://api.roblox.com/sign-out/v1 with your cookie in the headers. The XSRF token should be in the response headers.

How to avoid "HTTP/1.1 999 Request denied" response from LinkedIn?

I'm making request to LinkedIn page and receiving "HTTP/1.1 999 Request denied" response.
I use AWS/EC-2 and get this response.
On localhost everything works fine.
This is sample of my code to get html-code of the page.
<?php
error_reporting(E_ALL);
$url= 'https://www.linkedin.com/pulse/5-essential-strategies-digital-michelle';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);
var_dump($response);
var_dump($info);
I don't need whole page content, just meta-tags (title, og-tags).
Note that the error 999 don't exist in W3C Hypertext Transfer Protocol - HTTP/1.1, probably this error is customized (sounds like a joke)
LinkedIn don't allow direct access, the probable reason of them blocking any "url" from others webservers access should be to:
Prevent unauthorized copying of information
Prevent invasions
Prevent abuse of requests.
Force use API
Some IP addresses of servers are blocked, as the "IP" from "domestic ISP" are not blocked and that when you access the LinkedIn with web-browser you use the IP of your internet provider.
The only way to access the data is to use their APIs. See:
Accessing LinkedIn public pages using Python
Heroku requests return 999
Note: The search engines like Google and Bing probably have their IPs in a "whitelist".
<?php
header("Content-Type: text/plain");
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.linkedin.com/company/technistone-a-s-");
$header = array();
$header[] = "Host: www.linkedin.com";
$header[] = "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:50.0) Gecko/20100101 Firefox/50.0";
$header[] = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
$header[] = "Accept-Language: en-US,en;q=0.5";
$header[] = "Accept-Encoding: gzip, deflate, br";
$header[] = "Connection: keep-alive";
$header[] = "Upgrade-Insecure-Requests: 1";
curl_setopt($ch,CURLOPT_ENCODING , "gzip");
curl_setopt($ch, CURLOPT_HTTPHEADER , $header);
$my_var = curl_exec($ch);
echo $my_var;
LinkedIn is not supporting the default encoding 'identity' , so if you set the header
'Accept-Encoding': 'gzip, deflate'
you should get the response , but you would have to decompress it.
I ran into this while doing local web development and using the LinkedIn badge feature (profile.js). I was only getting the 999 Request denied in Chrome, so I just cleared my browser cache and localStorage and it started to work again.
UPDATE - Clearing cache was just a coincidence and the issue came back. LinkedIn is having issues with their badge functionality.
I submitted a help thread to their forums.
https://www.linkedin.com/help/linkedin/forum/question/714971

unable to get the website content by using file_get_content in php

When i am trying to get the website content from the external url fanpop.com by using file_get_contents in php, i am getting empty data. I used the below code to get the contents
$add_url= "http://www.fanpop.com/";
$add_domain = file_get_contents($add_url);
echo $add_domain;
but here i am getting empty result for $add_domain. But the same code is working for other urls and i tried to send the request from browser not from the script then also it is not working.
Below is the same request, but in CURL:
error_reporting(-1);
ini_set('display_errors','On');
$url="http://www.fanpop.com/";
$ch = curl_init();
$header=array('GET /1575051 HTTP/1.1',
'Host: adfoc.us',
'Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language:en-US,en;q=0.8',
'Cache-Control:max-age=0',
'Connection:keep-alive',
'Host:adfoc.us',
'User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36',
);
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,0);
curl_setopt( $ch, CURLOPT_COOKIESESSION, true );
curl_setopt($ch,CURLOPT_COOKIEFILE,'cookies.txt');
curl_setopt($ch,CURLOPT_COOKIEJAR,'cookies.txt');
curl_setopt($ch,CURLOPT_HTTPHEADER,$header);
echo $result=curl_exec($ch);
curl_close($ch);
... but the above is also not working, can any one tell is there any any changes have to make in that?
The problem with this particular site is that it only serves compressed contents and throws a 404 error otherwise.
Easy fix:
$ch = curl_init('http://www.fanpop.com');
curl_setopt($ch,CURLOPT_ENCODING , "");
curl_exec($ch);
You can also make this work for file_get_contents() but with a substantial amount of effort, as described in this article.

Categories