I'm using PHP & JSON to extract some data from a database.
This is my PHP file
<?php
error_reporting(0);
ini_set('error_reporting', E_ALL);
ini_set('display_errors','Off');
$mysqli = new mysqli("localhost", "root", $_REQUEST['password'], "");
if ($mysqli->connect_errno) {
echo "Failed to connect to DB.";
die();
} else {
$dbs = array();
$res = $mysqli->query("SHOW DATABASES");
$res->data_seek(0);
if ($res->num_rows > 0) {
while($row = $res->fetch_assoc()) {
$dbs[] = $row;
}
echo json_encode($dbs);
} else {
echo "Failed to get list of databases from server.";
die();
}} ?>
If the password is wrong, then the system outputs "Failed to connect to DB"
In my program, I have things to handle errors, but I am stuck at one part.
let urlString = "http://\(hostTextField.text):\(portTextField.text)/dblist.php? &password=\(passTextField.text)"
let url: NSURL = NSURL(string: urlString)!
let urlSession = NSURLSession.sharedSession()
println(url)
println(urlSession)
//2
let jsonQuery = urlSession.dataTaskWithURL(url, completionHandler: { data, response, error -> Void in
println(response)
println(data)
if (error != nil) {
println("Can't connect using credentials")
dispatch_async(dispatch_get_main_queue(), {
HUDController.sharedController.hide(afterDelay: 0.1)
})
sleep(1)
var refreshAlert = UIAlertController(title: "Camaleon Reports", message: "Can't connect to the database", preferredStyle: UIAlertControllerStyle.Alert)
refreshAlert.addAction(UIAlertAction(title: "Retry", style: .Default, handler: { (action: UIAlertAction!) in
println("Yes Logic")
}))
self.presentViewController(refreshAlert, animated: true, completion: nil)
return }
var err: NSError?
var jsonResult: [Dictionary<String, String>] = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as [Dictionary<String, String>]
// 3
if (err != nil) {
println("Still cant connect....")
println("JSON Error \(err!.localizedDescription)")
}
var jsonDB : [Dictionary<String, String>] = jsonResult
for currentDictionary in jsonDB{
var currentEntry = currentDictionary["Database"] as String!
My program crashes if I don't have the right password, but have the right IP address and Port/User for the MYSQL Database.
It crashes with this:
fatal error: unexpectedly found nil while unwrapping an Optional value
and points towards jsonResult. It makes sense, cause I don't retrieve two strings.
My problem is that if my password is off, then my PHP file echoes a string. How can I search for that string so that I can use an if statement and stop my application from crashing?
Your problem is likely in this line (wrapped for clarity):
var jsonResult: [Dictionary<String, String>] =
NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers,
error: &err) as [Dictionary<String, String>]
When your PHP script reports the error by just returning the string, it has returned invalid JSON. When you use NSJSONSerialization.JSONObjectWithData to parse it, that method will return nil if the JSON is invalid, as yours is.
You then take that value and assign it to a Swift variable that you've declared is not an optional. Trying to assign nil to a variable not declared with either ? or ! is a runtime error in Swift. (You don't get an error at compile time because you're using as to cast the value.)
One way to fix this would be to change your PHP so the error is proper JSON:
echo "{ \"error\": \"Failed to connect to DB.\" }"; # or something, my PHP is rusty
But that still leaves your Swift program in a fragile state; getting anything but proper JSON back from the server will make it crash.
Better is to declare the jsonResult variable as being an optional:
var jsonResult: [Dictionary<String, String>]? =
NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers,
error: &err) as [Dictionary<String, String>]?
Then in your code you can explicitly check whether jsonResult is nil, and if it is, you know an error has occurred, and can go back and look at the data object to see what it was.
Even that, though, can leave you in trouble. The root of a JSON document doesn't have to be a dictionary; it could be an array. And even if it is a dictionary, the values may not all be strings; they could be numbers, booleans, or nested arrays or dictionaries!
Objective-C's relatively lax type checking makes this easy to deal with, but Swift is stricter. Best might be to use one of the Swift-specific JSON libraries. That'll make your code far more robust.
Good luck!
There are two issues. One is the PHP and one is the Swift.
Your PHP really should never just report an error message. I'd suggest that it always return JSON. This will make it easier for your client code to detect and handle errors appropriately.
<?php
header("Content-Type: application/json");
$response = array();
error_reporting(0);
ini_set('error_reporting', E_ALL);
ini_set('display_errors','Off');
if (!isset($_REQUEST['password'])) {
$response["success"] = false;
$response["error_code"] = 1;
$response["error_message"] = "No password provided";
echo json_encode($response);
exit();
}
$mysqli = new mysqli("localhost", "root", $_REQUEST['password'], "");
if ($mysqli->connect_errno) {
$response["success"] = false;
$response["error_code"] = 2;
$response["mysql_error_code"] = $mysqli->connect_errno;
$response["error_message"] = $mysqli->connect_error;
echo json_encode($response);
exit();
}
if ($res = $mysqli->query("SHOW DATABASES")) {
$dbs = array();
$res->data_seek(0);
if ($res->num_rows > 0) {
while($row = $res->fetch_assoc()) {
$dbs[] = $row;
}
$response["success"] = true;
$response["results"] = $dbs;
} else {
$response["success"] = false;
$response["error_code"] = 3;
$response["error_message"] = "Failed to get list of databases from server.";
}
$res->close();
} else {
$response["success"] = false;
$response["error_code"] = 4;
$response["mysql_error_code"] = $mysqli->errno;
$response["error_message"] = $mysqli->error;
}
$mysqli->close();
echo json_encode($response);
?>
Note, this:
Specifies application/json header for Content-Type;
Always returns a dictionary, containing
a "success" key, which is either true or false;
if an error, an error_code indicating the type of error (1 = no password provided; 2 = connect failed; 3 = no databases found; 4 = some SQL error);
if an error, an error_msg string indicating the error message string; and
if a success, a results array (much like you used to return at the root level).
On the Swift side, you need to :
Change it to look for these various server app-level errors (note, I make the top level structure a dictionary, and your original array of dictionaries a particular value;
You might want to proactively check the statusCode of the response object, to make sure the server gave you a 200 return code (e.g. 404 means that the page was not found, etc.);
You might also want to check for JSON parsing errors (in case some bug in the server prevented well-formed JSON from being returned); and
You really should be percent-escaping the password (because if it included + or & characters, it wouldn't get transmitted successfully otherwise).
Thus, you might have something like:
let encodedPassword = password.stringByAddingPercentEncodingForURLQueryValue()!
let body = "password=\(encodedPassword)"
let request = NSMutableURLRequest(URL: URL!)
request.HTTPBody = body.dataUsingEncoding(NSUTF8StringEncoding)!
request.HTTPMethod = "POST"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
// detect fundamental network error
guard error == nil && data != nil else {
print("network error: \(error)")
return
}
// detect fundamental server errors
if let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode != 200 {
// some server error
print("status code was \(httpResponse.statusCode); not 200")
return
}
// detect parsing errors
guard let responseObject = try? NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String : AnyObject] else {
// some problem parsing the JSON response
print(String(data: data!, encoding: NSUTF8StringEncoding))
return
}
// now parse app-level response to make sure `status` was `true`
guard let success = responseObject!["success"] as? NSNumber else {
print("Problem extracting the `success` value") // we should never get here
return
}
if !success.boolValue {
print("server reported error")
if let errorCode = responseObject!["error_code"] as? NSNumber {
switch (errorCode.integerValue) {
case 1:
print("No password provided")
case 2:
print("Connection failed; probably bad password")
case 3:
print("No databases found")
case 4:
print("Some SQL error")
default:
print("Unknown error code: \(errorCode)") // should never get here
}
}
if let errorMessage = responseObject!["error_message"] as? String {
print(" message=\(errorMessage)")
}
return
}
if let databases = responseObject!["results"] as? [[String : AnyObject]] {
print("databases = \(databases)")
}
}
task.resume()
The percent-escaping code is in a String category:
extension String {
// see RFC 3986
func stringByAddingPercentEncodingForURLQueryValue() -> String? {
let characterSet = NSCharacterSet(charactersInString:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
return self.stringByAddingPercentEncodingWithAllowedCharacters(characterSet)
}
}
A couple of other ancillary observations:
Never send a passwords in the clear. Put them in the body of a POST request (not the URL), and then use a https URL.
I'd personally not use the MySQL password part of the app-level authentication. I'd keep MySQL authentication logic encoded on the server-side, and then use your own user authentication table.
Related
I am currently in the process of learning how to retrieve encoded JSON data from a mySQL database on an apache server and decode the JSON into an instance of my own custom struct;
struct Person: Codable, FetchableRecord, MutablePersistableRecord {
var id: Int64
var firstName: String
var lastName: String
}
here is my method for the network request on xcode
func dataRequestDownload() {
let baseURL = URL(string: "http://example.com/db_request.php?action=request")
DispatchQueue.main.async {
if let url = baseURL {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
let decoder = JSONDecoder()
let person = try? decoder.decode(Person.self, from: data)
print(person)
}
}
task.resume()
}
}
}
}
My issue is that person prints as nil which makes me think the data isnt being decoded properly.
This is my PHP script for the GET request.
<?php
$con=mysqli_connect("127.0.0.1","root","example_password","example_database");
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
$sql = "SELECT * FROM person";
if ($result = mysqli_query($con, $sql))
{
// If so, then create a results array and a temporary one
// to hold the data
$resultArray = array();
$tempArray = array();
// Loop through each row in the result set
while($row = $result->fetch_object())
{
// Add each row into our results array
$tempArray = $row;
array_push($resultArray, $tempArray);
}
// Finally, encode the array to JSON and output the results
echo json_encode($resultArray);
}
// Close connections
mysqli_close($con)
?>
Finally I am not sure this info matters, but in the mySQL database the table is set up just like my struct where id is the PRIMARY key. If more information is needed let me know in the comments, but as far as I know this is all that seems connected to my issue.
Edit: Some other possibly important information is when calling print(data) and print(response) I get
48 bytes
{ Status Code: 200, Headers {
Connection = (
"Keep-Alive"
);
"Content-Length" = (
48
);
"Content-Type" = (
"text/html; charset=UTF-8"
);
Date = (
"Thu, 17 Jun 2021 18:43:02 GMT"
);
"Keep-Alive" = (
"timeout=5, max=100"
);
Server = (
"Apache/2.4.46 (Win64) PHP/7.3.21"
);
"X-Powered-By" = (
"PHP/7.3.21"
);
} })
the URL is exempt from this of course.
Per the request in the comments; having done this
let object = try? JSONSerialization.jsonObject(with: data)
print(object)
I get
Optional(<__NSSingleObjectArrayI 0x281510080>(
{
firstName = John;
id = 0;
lastName = Doe;
}
)
)
Edit2: Upon running
do {
let person = try decoder.decode([Person].self, from: data)
print(person)
} catch {
print(error)
}
the following error appears
typeMismatch(Swift.Int64, Swift.DecodingError.Context(codingPath [_JSONKey(stringValue: "Index 0", intValue: 0),
CodingKeys(stringValue: "id", intValue: nil)], debugDescription:
"Expected to decode Int64 but found a string/data instead.", underlyingError: nil))
Here is the actual JSON upon visiting the URL
[{"id":"0","firstName":"John","lastName":"Doe"}]
When decoding JSON, it's helpful to use do/try/catch so that you can actually see what the error is.
It looks like, in this case, you have an array (which is clear from your PHP code), but you're trying to decode a single object. Try this:
do {
let person = try decoder.decode([Person].self, from: data)
print(person)
} catch {
print(error)
}
Then, since you'll have an array in person (which might be more accurately named people at this point), you'll want to access it with something like person.first
Update, based on added code in your edits:
That error is telling you that id is a String in the JSON. Either adjust your Person model to use String for the type of id instead of Int64, or adjust your database and or PHP code to use a number instead of a String for the id.
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 5 years ago.
Improve this question
I have a problem on Xcode using Swift 3.0, i want to store information in a mysql database with php. The php code is reached but always return failure on the insert request. I don't know, it works fine with android. Here's my code :
Swift :
#IBAction func SignUp(_ sender: UIButton) {
var Name1: String = Name.text!
var Prenom: String = PRENAME.text!
var add: String = Addr.text!
var code:String = CP.text!
var mail:String = Email.text!
var pass:String = password.text!
var request = URLRequest(url: URL(string: "http://www.example.com/myscript.php")!)
request.httpMethod = "POST"
let postString = "name=\(Name1)&pname=\(Prenom)&add=\(add)&pc=\(code)&mail=\(mail)&pass=\(pass)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString)")
}
task.resume()
}
And my PHP code :
<?php
$reponse = array();
$pseudo = $_POST['name'];
$score = $_POST['pname'];
$add = $_POST['add'];
$pc = $_POST['pc'];
$mail = $_POST['mail'];
$password = $_POST['pass'];
$mysqli = new MySQLi("host","root","pass","user");
$result = $mysqli->query("INSERT INTO compte (Name,Name2,Ad,code,maiil,pass) VALUES('$pseudo','$score','$add','$pc','$mail','$password')");
if ($mysqli->connect_error){
die('Connect Error (' . $mysqli->connect_errno . ') '
. $mysqli->connect_error);
}
if($result){
$response["success"] = 1;
$response["message"] = "SUCCESS!";
echo json_encode($response);
}
else{
$response["success"] = 0;
$response["message"] = "FAILURE!";
echo json_encode($response);
}
A couple of thoughts:
On failure, you should set message to $mysqli->error instead of just "FAILURE!", so you can see why it failed.
I'd suggest moving the "did connection fail" logic before you attempt the query. I'd also suggest you change this to output JSON that says that the connection failed and exit, rather than just performing die. Later, as you improve the Swift code to parse errors, it will be useful to keep all server messages as JSON.
You should change the PHP to escape the parameters using real_escape_string. In the absence of that, if any of your values included a ' character, your SQL will fail. (You're also susceptible to SQL injection attacks.)
You should change the Swift to percent escape the values included in the httpBody of the request. See https://stackoverflow.com/a/28027627/1271826 for examples. Or use something like Alamofire that takes care of this for you. But as it stands:
If any of these values included a + character, it would likely be replaced with a space.
If any of these values included a & character, it would end up truncating that field.
If any of these values included any other reserved character, the whole query could fail.
While not essential, I'd suggest :
In the Swift code, set the Content-Type header of the request to application/x-www-form-urlencoded and the Accept header of the request to application/json.
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
In the PHP code, set the Content-Type header to application/json before you echo the JSON:
header("Content-Type: application/json");
While this is optional, if you later adopt a library, like Alamofire, which performs validation for these standard headers, it will make your life easier.
don't forget to add this to info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
so you can fire http or https requests
-- make sure any of you parameters don't contain & so value can be sent successfully or replace it with %26
Sorry, my bad everything is OK, by doing the sqli->error, i have found the error, i have forgotten that i don't let duplicate the mail in my database. So thank you very much everyone. And have a good week end !
I am trying to send an image to server, but the image should be in Base64 format. I am using this function to send it:
func upload_signature_staff(ticket: NSString){
let defaults = NSUserDefaults.standardUserDefaults()
let stringOne = defaults.stringForKey(defaultsKeys.staff_id)
let stringtwo = defaults.stringForKey(defaultsKeys.mst_customer)
let sig = defaults.stringForKey("staff_signature")
let request = NSMutableURLRequest(URL: NSURL(string: "http://xxxxxxxxxxxxx/upload.php")!)
request.HTTPMethod = "POST"
let postString = "action=add_signature&mst_customer=\((stringtwo!))&ticket=\((ticket))&signature=\((sig!))¤t_user=\((stringOne!))&item_type=10"
print(postString)
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard error == nil && data != nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? NSHTTPURLResponse where httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("responseString = \(responseString)")
}
task.resume()
//self.performSegueWithIdentifier("goto_main2", sender: self)
}
The sig variable holds the Base64 string, it is being printed in my console so I can verify that the string is correct. I am also printing the postString and upon inspection it is also correct that the signature is matching the Base64 String. But when I open phpmyadmin, I see the field of my image with incomplete Base64 string, maybe 1/4 is just there.
Here's my php code, in case you want to see it:
<?php
require_once("../c.php");
$action = trim($_POST['action']);
if($action == "add_signature"){
$mst_customer = trim($_POST['mst_customer']);
$ticket = trim($_POST['ticket']);
$signature = trim($_POST['signature']);
$current_user = trim($_POST['current_user']);
$item_type = trim($_POST['item_type']);
$inputtime = time();
$sql = "INSERT INTO ticket_items SET mst_customer = '$mst_customer', ticket = '$ticket', ticket_item_type = '$item_type', details = '$signature', addedby = '$current_user', lastupdate = '$inputtime' ";
mysql_query($sql) or die(mysql_error());
}
?>
I think this was solved in the comments but here's a recap:
Inserting base64 strings that were too long (variable length?) in a varchar(255) field resulted in missing data. As far as I can tell, increasing the size of the field solved the immediate problem. I use the term "immediate" because, as #YvesLeBorg pointed out in the comments, this is bound to fail at some point without input size restrictions on the backend.
Additionally, I couldn't ignore the fact that the PHP/SQL code was wide open to injections.
Passing $mst_customer = trim($_POST['mst_customer']); on to "INSERT INTO ticket_items SET mst_customer = '$mst_customer' and then executing via mysql_query($sql) or die(mysql_error()); is dangerous!
Anybody could write anything in the $_POST parameter and SQL would happily accept it. Prepared statements, PDO, input sanitization etc. are there for a reason.
Finally, there was an issue concerning vanishing + signs in the base64 data. This was the result of missing url encoding of the post data.
I think that sums it up.
The goal is to display a result of json_encode result from php into swift as a label. I have referenced to many tutorials and practices of parsing json with swift, but I still cant understand the issue.
I referenced to an example that uses date.jsontest.com and that works fine.
In my project, I have parsed this simple JSON object to start:
[{"username":"user"}]
Here is the swift code:
override func viewDidLoad() {
super.viewDidLoad()
// 1
//let urlAsString = "http://date.jsontest.com"
let urlAsString = "http://(ip address)/service.php"
let url = NSURL(string: urlAsString)!
let urlSession = NSURLSession.sharedSession()
// 2
let jsonQuery = urlSession.dataTaskWithURL(url, completionHandler: { data, response, error -> Void in
if (error != nil) {
println(error.localizedDescription)
}
var err: NSError?
// 3
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if (err != nil) {
println("JSON Error \(err!.localizedDescription)")
}
// 4
//let jsonDate: String! = jsonResult["date"] as NSString
//let jsonId = jsonResult["id"] as NSString
//let jsonTime: String! = jsonResult["time"] as NSString
let jsonUser: String! = jsonResult[0] as NSString
dispatch_async(dispatch_get_main_queue(), {
//self.dateLabel.text = jsonId
//self.timeLabel.text = jsonUser
self.dateLabel.text = jsonUser
})
})
// 5
jsonQuery.resume()
//searchItunesFor("JQ Software")
//startConnection()
// Do any additional setup after loading the view, typically from a nib.
}
The code runs fine when I use http://date.jsontest.com as the string along with jsonDate and jsonTime.
I tried to understand the debugger in Xcode, but as a new user to Swift, its hard to understand what the issue is. All I could root out is that there is an issue with how my JSON is formatted.
Any tips are greatly appreciated, thanks.
EDIT:
Here is the code http://(ip address)/service.php that gives me the JSON
<?php
//connect to MySQL
// Create connection
$con=mysqli_connect($hostname,$username,$password,$database);
// Check connection
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
// This SQL statement selects ALL from the table 'Locations'
$sql = "SELECT username FROM members WHERE id = 1";
// Check if there are results
if ($result = mysqli_query($con, $sql))
{
// If so, then create a results array and a temporary one
// to hold the data
$resultArray = array();
$tempArray = array();
// Loop through each row in the result set
while($row = $result->fetch_object())
{
// Add each row into our results array
$tempArray = $row;
array_push($resultArray, $tempArray);
}
// Finally, encode the array to JSON and output the results
echo json_encode($resultArray);
}
// Close connections
mysqli_close($con);
?>
I am getting response from php to android, see below .php
<?php
$username = $_POST[username];
$password = $_POST[password];
if($username == "himm")
{
if($password == "rangde"){
echo "success" ;
}
else{
echo "passwor is wrong.";
}
}
else
{
echo "fail";
}
?>
i am getting success in logcat window of android. But here in android i have made comparison like below,
BufferedReader reader = new BufferedReader(new InputStreamReader(is,"iso-8859-1"),8);
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
is.close();
result = sb.toString();
Log.v("","Result : "+result);
Log.v("","this is Result: "+result.equalsIgnoreCase("success"));
if(result.equals("success")){
Log.v("","Login successfully......");
}
else{
Log.v("","Fail to login......");
}
but in logcat window i see "fail to login" message. Php send response as "success" but which type ?
Here condition of if(result.equals("success")) should be true. Please any body give me idea or suggestions to achieve thies..........thank you in advance
sb.append(line + "\n"); modifies the 'success' to 'success\n' so the if(result.equals("success")){ fails because 'success\n' does not match 'success'.
In your android code, you add a trailing LineFeed to the result you receive from php :
sb.append(line + "\n");
So you actually compare 'success' to 'success\n' which is false.
In addition to previously posted answer (which are correct), I would like to point out that HTTP contains mechanisms to do what you are trying to do.
Http authentication allows you to use standard Http return codes (200 in case of success, 401 in case of authentication failure) and to use existing systems to handle that part (most frameworks will provide such).
It also allows you to separate authentication from the rest of the message you send back from the server, and to compare the authentication status at soon as you receive the headers from the server.