Qt code comprehension - qt

i'm struggling with a code snippet for days now, i was wondering if someone could help me understand this code snippet. i'm not asking for code whatsoever, just someone to explain to me this please : (a uri appear to be the complete url to a service)
void RestClient::_prepareRequest( QNetworkRequest& a_request, const QString& a_uri ){
QSslConfiguration config(QSslConfiguration::defaultConfiguration());
config.setProtocol(QSsl::SslV3);
config.setSslOption(QSsl::SslOptionDisableServerNameIndication, true);
a_request.setSslConfiguration(config);
a_request.setRawHeader("Accept", "application/xml");
a_request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
QByteArray l_api_key; l_api_key.append( toQString( m_api_key) );
QByteArray l_request_hash;
l_request_hash.append( toQString( _buildRequestHash( toStlString(a_uri) ) ) );
a_request.setRawHeader("EMApikey", l_api_key );
a_request.setRawHeader("EMRequestHash", l_request_hash );
a_request.setUrl( QUrl( a_uri ) );
}

So what you've got there is a function taking two parameters, a reference to a QNetworkRequest and a constant reference to a QString for the URI you wish to access. The next lines sets QSslConfiguration to get the default SSL configuration for Qt's network access, and stores it in config. It then sets some further QSsl options and then sets the a_request's SSL settings to be provided by the config you've just set.
Next up it sets some HTTP headers for the request, so these are reasonably standardised, so the Accept references what kind of information is acceptable for the response from the server which in this case is xml (Accept header documentation). The Content-type tells the receiving server what sort of data you're sending in the request body.
The final stage sets a non-standard HTTP header, which is for the application API access key, after that it sets the URL you originally passed and the function is complete. After that the QNetworkRequest can be used with QNetworkAccessManager to send a request to a server, with an API key encoded in, and you'll receive an XML response in return.

Related

'Access-Control-Allow-Origin' missing using actix-web

Stuck on this problem where I received this error everytime making POST request to my actix-web server.
CORS header 'Access-Control-Allow-Origin' missing
my javascript (VueJs running on localhost:3000) :
let data = //some json data
let xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:8080/abc");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = () => {
console.log(xhr.responseText);
}
xhr.send(JSON.stringify(data));
My Actix_Web server (running on localhost:8080) :
#[actix_web::main]
async fn main() {
HttpServer::new(move || {
let cors = Cors::default()
.allowed_origin("http://localhost:3000/")
.allowed_methods(vec!["GET", "POST"])
.allowed_header(actix_web::http::header::ACCEPT)
.allowed_header(actix_web::http::header::CONTENT_TYPE)
.max_age(3600);
App::new()
.wrap(cors)
.service(myfunc)
})
.bind(("0.0.0.0", 8080))
.unwrap()
.run()
.await
.unwrap();
}
my cargo.toml dependencies
[dependencies]
actix-web = "4"
actix-cors = "0.6.1"
...
Got any idea?
Okay, so I've done some testing. If you're writing a public API, you probably want to allow all origins. For that you may use the following code:
HttpServer::new(|| {
let cors = Cors::default().allow_any_origin().send_wildcard();
App::new().wrap(cors).service(greet)
})
If you're not writing a public API... well, I'm not sure what they want you to do. I've not figured out how to tell the library to send that header. I guess I will look at the code.
UPDATE:
So funny story, this is how you allow specific origins:
let cors = Cors::default()
.allowed_origin("localhost:3000")
.allowed_origin("localhost:2020");
BUT, and oh boy, is that but juicy. The Access-Control-Allow-Origin response header is only set when there is a Origin request header. That header is normally added by the browser in certain cases 1. So I did that (using the Developer tools in the browser). What did I get? "Origin is not allowed to make this request". I set my origin header to localhost:3000. Turns out, the arctix library simply discards that header if no protocol was provided... (e.g. http://) (I assume it discards it, if it deems its format invalid). That internally results in the header being the string "null". Which is, checks notes, not in the list of allowed origins.
And now the grand finale:
Your origin header needs to be set to (by either you or the browser): "http://localhost:3000".
Your configuration needs to include: .allowed_origin("http://localhost:3000").
After doing that, the server will happily echo back your origin header in the Access-Control-Allow-Origin header. And it will only send that one.
I've no idea if any of that is what the standard specifies (or not). I encourage you to read through it, and if it doesn't comply, please open an issue on GitHub. I would do it myself, but I'm done with programming for today.
Cheers!

How to intercept request from QWebEngineView and serving them directely from the application?

Is there a way with QWebEngineView to intercept an http request, and to serve it server-less from the app ?
I heard about QWebEngineUrlRequestInterceptor and acceptNavigationRequest(), but they provide only inspection on requests, and redirection for get... But I would like to make the http response from the Qt app.
(I added pyqt in the tags because I would use it from python, but a c++ answer is acceptable too)
To intercept http request you will need to use this code:
// on app startup
QWebEngineProfile.defaultProfile().installUrlSchemeHandler(new
QByteArray("https"), new QWebEngineUrlSchemeHandler() {
#Override public void requestStarted(QWebEngineUrlRequestJob job) {
final String url = job.requestUrl().url();
if (**some url not ok condition**) {
job.fail(QWebEngineUrlRequestJob.Error.UrlInvalid);
}
String data = loadSomeData();
if (data != null) {
QBuffer buffer = new QBuffer();
// this is IMPORTANT! or you will have memory leaks
job.destroyed.connect(buffer::disposeLater);
buffer.open(QIODeviceBase.OpenModeFlag.WriteOnly);
buffer.write(data.getBytes(StandardCharsets.UTF_8));
buffer.close();
job.reply(new QByteArray("text/html"), buffer);
}
}
});
My versions is for QTJambi(Java), but it's not hard to convert it to C++/Python
The qt documentation says the redirection is only for GET request. However, when trying it out (PyQt6==6.4.0) we found out that this is actually not true. If you redirect a POST request in the WebEngine to a local webserver listening on localhost, you will actually receive the request including the payload.
Perhaps this is because Webkit doesn't include the payload for redirect and Chromium does? (I couldn't find docs stating the difference.)
from PyQt6.QtCore import QUrl
from PyQt6.QtWebEngineCore import QWebEngineUrlRequestInterceptor
class WebEngineUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
def interceptRequest(self, info):
method = info.requestMethod()
if method == "POST":
if info.requestUrl().url() == "https://my-url-to-something":
info.redirect(QUrl("http://127.0.0.1:8000"))

Connecting Qt with SSL to a jetty server

I have some problems with connecting a qt client to an embedded jetty server.
At first, I use the following components:
Qt 4.4.3 (compiled with active openssl support)
jetty 8.8.1
java 6
I know, the versions are not most recent, but because of licencing issues and customer wishes I can not use newer one.
So, the scenario is that a qt client has to send http GET and POST requests to the jetty server. As long I use simple http with the QHttp object it works fine, the problems start when I switch to SSL.
My first try was to use the QSslSocket object for the GET request:
// Load certs + private key to socket
_pSocket = new QSslSocket(this);
_pSocket->setLocalCertificate(_certificate);
_pSocket->setPrivateKey(_privatekey);
_pSocket->addDefaultCaCertificate(_cacertificate);
connect (_pSocket, SIGNAL(encrypted()), this, SLOT(_encrypted()));
_pSocket->connectToHostEncrypted("localhost", 8000);
with the following slot function for the encrypted state:
void TestClient::_encrypted() {
QString _path("/testpath/list");
QByteArray buffer("GET ");
buffer.append(_path).append(" HTTP/1.1\r\n");
_pSocket->write(buffer);
}
Here I have my first problem:
This results in the following string, which is as far as I see compliant to RFC 2616:
"GET /testpath/list HTTP/1.1\r\n"
For some reason, the jetty server has a problem with that, keeping in a loop till the client close the connection because of a time out.
But if I use the following string, it works perfect:
"GET /testpath/list\r\n"
Here is my first question: Do you now an explanation for this behaviour ? I can live with it, but I want to know the reason
My second problem is the POST request, this fails always.
These examples I already tried:
"POST /testpath/receive/\r\n{"data":"hello world ?!"}\r\n"
"POST /testpath/receive/ HTTP/1.1\r\n{"data":"hello world ?!"}\r\n"
"POST /testpath/receive/\r\n\r\n{"data":"hello world ?!"}\r\n"
"POST /testpath/receive/ HTTP/1.1\r\n\r\n{"data":"hello world ?!"}\r\n"
I have the feeling, that the body is every time empty, so my server crashes because he tries to parse an empty string as json.
At least, the following log shows that:
2013-11-19 17:11:51.671, INFO, foo.bar.RepositoryHandler, qtp11155366-16 - /testpath/receive request type : /receive
2013-11-19 17:11:51.811, ERROR, foo.bar.RepositoryHandler, qtp11155366-16 - /testpath/receive missing or unknown elements in JSON request. Check JSON against documentation
2013-11-19 17:11:51.874, WARN, org.eclipse.jetty.server.AbstractHttpConnection, qtp11155366-16 - /testpath/receive /testpath/receive
java.lang.NullPointerException: null
at foo.bar.RepositoryHandler.decodeViewingRequest(RepositoryHandler.java:366) ~[MyServer.jar:na]
at foo.bar.RepositoryHandler.handle(RepositoryHandler.java:182) ~[MyServer.jar:na]
So, all together, I think I have several major errors in my requests. But which ?
My second try was to use the QHttp object and change the QSocket it uses with a QSslSocket I already initiated.
Here's the code of the main function:
QSslSocket* _pSocket;
QHttp* _pHttp;
int _id;
QBuffer* _pBuffer;
QByteArray _data;
_pSocket = new QSslSocket(this);
_pSocket->setLocalCertificate(_certificate);
_pSocket->setPrivateKey(_privatekey);
_pSocket->addDefaultCaCertificate(_cacertificate);
QUrl url;
url.setScheme("https");
url.setHost("localhost");
url.setPort(8001);
url.setPath("/testpath/receive");
connect (_pSocket, SIGNAL(encrypted()), this, SLOT(_encrypted()));
connect(_pHttp,SIGNAL(requestFinished(int,bool)),this,SLOT(_requestFinished(int,bool)));
connect(_pHttp,SIGNAL(done(bool)),this,SLOT(_done(bool)));
_pBuffer = new QBuffer(&_data);
_pHttp->setSocket(_pSocket);
_pSocket->connectToHostEncrypted(strHost, strPort.toInt());
_id = _pHttp->get(url.toString(),_pBuffer);
And the callbacks:
void _requestFinished(int id, bool error) {
if(id = _id)
qDebug() << "data=" << _data;
}
void _encrypted() {
qDebug() << "encrypted";
}
void _done(bool error) {
logInfo() << "_done";
if(_pHttp) {
_pHttp->abort();
delete _pHttp;
_pHttp = 0;
}
if(_pBuffer) {
delete _pBuffer;
_pBuffer = 0;
}
if(_pSocket) {
_pSocket->disconnectFromHost();
delete _pSocket;
_pSocket = 0;
}
}
I think, I only have to change the position of the _pHttp->get call, perhaps in the _encrypted callback, but I'm not sure.
Some good advise ?
Thanks,
Robert
Your HTTP request is incomplete, per RFC2616.
"GET /testpath/list HTTP/1.1\r\n"
That is invalid.
Try this instead.
"GET /testpath/list HTTP/1.1\r\n" + /* request line (required) */
"Host: localhost\r\n" + /* host header (required minimum) */
"\r\n" /* terminating CR + LF (required) */
As outlined in Section 5.1.2
The most common form of Request-URI is that used to identify a
resource on an origin server or gateway. In this case the absolute
path of the URI MUST be transmitted (see section 3.2.1, abs_path) as
the Request-URI, and the network location of the URI (authority) MUST
be transmitted in a Host header field. For example, a client wishing
to retrieve the resource above directly from the origin server would
create a TCP connection to port 80 of the host "www.w3.org" and send
the lines:
GET /pub/WWW/TheProject.html HTTP/1.1
Host: www.w3.org
The Request-URI line and Host header Header are mandated.

Logging into a remote website with QT5

I am attempting to create an application that can login to a website. The specific website is:
http://adfast.biz
Here is the code I am currently using:
void MainWindow::http_finish(QNetworkReply *reply)
{
int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (code >= 300 && code < 400)
{ //HTTP 3XX codes are redirections
QUrl redirectTo = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
reply->manager()->get(QNetworkRequest(redirectTo));
return;
}
if (reply->error() == QNetworkReply::NoError)
{
QString Msg = QString::fromUtf8(reply->readAll());
if (cdone == 0)
{//Runs only once, causing a reload of the main page
++cdone;
QUrl URL("http://adfast.biz");
QNetworkRequest QNR(URL);
reply->manager()->get(QNR);
QMessageBox::information(0,"1)" + QString::number(code),Msg);
return;
}
QMessageBox::information(0,"2)" + QString::number(code),Msg);
}
else
{
QMessageBox::information(0,"Error:",reply->errorString());
}
reply->deleteLater();
}
void MainWindow::on_Send_clicked()
{
QNetworkAccessManager* MNAM = new QNetworkAccessManager(this); //Stored within QNetworkReply->manager()
connect(MNAM,SIGNAL(finished(QNetworkReply*)),this,SLOT(http_finish(QNetworkReply*)));
QUrlQuery postData;
postData.addQueryItem("email","mail#mail.net");
postData.addQueryItem("senha","Password");
postData.addQueryItem("logar","ok");
QUrl URL(ui->TXT_Input->toPlainText());
URL.setQuery(postData);
QNetworkRequest QNR(URL);
QNR.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
MNAM->post(QNR,URL.toEncoded());
}
I am guessing that I either am sending the information the wrong way or that I need to possibly manage cookies? Both responses come back with HTTP status code: 200. The first comes with no source the second comes with the full web-source but is NOT logged in. I am positive that the user-data being sent is correct, but not that it is being sent correctly.
Edit:
I've changed a little, with no luck. First I have added a cookie-jar using:
QNetworkAccessManager* MNAM = new QNetworkAccessManager(this); //Stored within QNetworkReply->manager()
QNetworkCookieJar* cJar = new QNetworkCookieJar;
MNAM->setCookieJar(cJar);
connect(MNAM,SIGNAL(finished(QNetworkReply*)),this,SLOT(http_finish(QNetworkReply*)));
Then, I tested to see if any cookies are being received using the following code at the top of MainWindow::http_finish:
QList<QNetworkCookie> cookies = reply->manager()->cookieJar()->cookiesForUrl(QUrl("http://adfast.biz/"));
QMessageBox::information(0,"Cookies",QString::number(cookies.count()));
I want to add that the post is being sent to: http://adfast.biz/login (That is the value of: ui->TXT_Input->toPlainText()) But it seems that I can't get this to login at all. And I am not sure what I'm missing.
So, I did manage to login into this site with testguest#yahoo.com and mypassword12. But I can only offer very quick and dirty code, which explains how. The 'polishing' you must do yourself. :-)
QFile f("/tmp/cookie.txt");
f.open(QIODevice::ReadOnly);
QDataStream s(&f);
while(!s.atEnd()){
QByteArray c;
s >> c;
QList<QNetworkCookie> list = QNetworkCookie::parseCookies(c);
qDebug() << "eee" << list;
jar->insertCookie(list.at(0));
}
connect(MNAM,SIGNAL(finished(QNetworkReply*)),
this,SLOT(http_finish(QNetworkReply*)));
QUrlQuery postData;
postData.addQueryItem("email","testguest#yahoo.com");
postData.addQueryItem("senha","mypassword12");
postData.addQueryItem("logar","ok");
//QUrl URL("http://adfast.biz/");
//QUrl URL("http://adfast.biz/anuncios_telexfree");
URL.setQuery(postData);
QNetworkRequest QNR(URL);
QNR.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
MNAM->get(QNR);
Above code reads the cookies, which are written in the http_finish slot below into /tmp/cookie.txt.
The first time you run this code, you must uncomment the first URL, the second time, when you have your cookies, the second URL. Ignore that I made your post into a get. I did it just for debugging reasons.
void MainWindow::http_finish(QNetworkReply *reply){
qDebug() << reply->readAll();
QList<QNetworkCookie> list =
MNAM->cookieJar()->cookiesForUrl(QUrl("http://adfast.biz/"));
QFile f("/tmp/cookie.txt");
f.open(QIODevice::ReadWrite);
for(int i = 0; i < list.size(); ++i){
QDataStream s(&f);
s << list.at(i).toRawForm();
}
f.close();
}
The code above writes the cookies from http://adfast.biz/ into /tmp/cookie.txt. Below an example of a cookie, which I received:
2-Sep-2013 16:41:39 GMT; domain=adfast.biz; path=/3 16:41:39 GMT;
domain=adfast.biz; path=/n=adfast.biz; path=/
Summary: You must connect to http://adfast.biz/ to get your cookie. When you have it, you must again connect, but this time to http://adfast.biz/anuncios_telexfree.
You may need to add a QNetworkCookieJar to your QNetworkAccessManager for keeping and sending cookies between requests and responses. You can set your cookie jar by using QNetworkAccessManager::setCookieJar(QNetworkCookieJar* cookieJar). Have a look at the requests being sent using HttpFox in Firefox and you can see if there are any cookies being sent back and forth.
Try to connect:
connect(&MNAM,SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
this,SLOT(provideAuthentication(QNetworkReply*,QAuthenticator*)));
And create a slot:
void TrackerClient::provideAuthentication(QNetworkReply *reply,
QAuthenticator *auth){
Q_UNUSED(reply);
auth->setUser(<your username>);
auth->setPassword(<your password>);
}
Ok, second try. The idea to add a cookiejar is correct, but just adding the jar is not enough. I tried the following with another site, which requires login. I did work. For your site I don't have login and password, and since it is not English, it is a little bit too hard for me for a quick help. :-)
How the login procedure works on "my" site.
You go to the main url, e.g. www.mysite.something.
The site asks you for a cookie. You have none.
Your are redirected (status 302 temporary moved) to a
page www.mysite.something/takelogin.php <--- example.
You enter your credentials. You already do this in your postdata.addQuery calls.
You send your post. If your creds are ok, the site sends you a cookie.
So far so good. What did you wrong? Now that you have the cookie you must again go to www.mysite.something. You are not automatically logged in and redirected to the main page of your site. And keep in mind, that cookies in QNetworkCookieJar are not stored on disk. They are only kept in memory. So, if your jar/MNAM is deleted, everything is gone.
You can easily store your received cookies to disk using the toRawForm() method of QNetworkCookie. To restore the cookie use the static QNetworkCooky::parseCookies(const QByteArray &cookieString) method.
Oh, almost forgot: The main url www.mysite.something did not send any cookies. I had to follow the redirect www.mysite.something/takelogin.php.
Disclaimer: It worked on my site. I don't know, if this is general for all sites, which require a login.
Checked with wrong creds: I was redirected to adfast.biz/login. This seems to be your redirect login page. More I cannot do without real creds.

How to return error pages with body using HttpListener/HttpListenerResponse

I'm in the process of creating a REST API using HttpListener in .NET (C#). This all works out great, except for one slight issue.
I'm trying to return responses with Status Codes other than OK (200), for instance ResourceNotFound (404).
When I set the StatusCode of the HttpListenerResponse to something other than 200, and create a response body (using HttpListenerResponse.OutputStream), it seems to be resetting the status code to 200. I'm not able to send a response with StatusCode 404 and a message body. However, this should be possible according to the HTTP specs. I'm checking the requests and responses with Fiddler, but I'm not able to get what I'm looking for.
I've had the same problem and found the source of the problem :
If you write the body in the OutputStream before set the StatusCode (or any other property), the response will be sent before the modification is applied !
So, you have to proceed in this order :
public void Send(HttpListenerContext context, byte[] body)
{
// First, set a random status code and other stuffs
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Response.ContentType = "text/plain";
// Write to the stream IN LAST (will send request)
context.Response.OutputStream.Write(body, 0, body.Length);
}

Resources