Subject: [pgAdmin4][Patch]: Fixed RM 1603 & RM 1220

From: Khushboo Vashi <[email-masked]>
Date: Fri, 14 Oct 2016 18:58:12 +0530

Hi,

Please find the attached patch to fix the below 2 bugs.

RM 1603: [Web Based] Export database failed if object contains double
quotes.
RM 1220: Backup database is not working with special characters

The issues which were fixed:

1. Client side data were not unescaped
2. Required command line arguments were quoted twice

Thanks,
Khushboo
diff --git a/web/pgadmin/tools/backup/__init__.py b/web/pgadmin/tools/backup/__init__.py
index 771b3ff..acfc6e1 100644
--- a/web/pgadmin/tools/backup/__init__.py
+++ b/web/pgadmin/tools/backup/__init__.py
@@ -276,7 +276,7 @@ def create_backup_job(sid):
         server.username,
         '--no-password',
         '--database',
-        driver.qtIdent(conn, server.maintenance_db)
+        server.maintenance_db
     ]
     if 'role' in data and data['role']:
         args.append('--role')
@@ -428,14 +428,14 @@ def create_backup_objects_job(sid):
     set_value('no_of_jobs', '--jobs', True)
 
     for s in data['schemas']:
-        args.extend(['--schema', driver.qtIdent(conn, s)])
+        args.extend(['--schema', s])
 
     for s, t in data['tables']:
         args.extend([
             '--table', driver.qtIdent(conn, s, t)
         ])
 
-    args.append(driver.qtIdent(conn, data['database']))
+    args.append(data['database'])
 
     try:
         p = BatchProcess(
diff --git a/web/pgadmin/tools/backup/templates/backup/js/backup.js b/web/pgadmin/tools/backup/templates/backup/js/backup.js
index a13a31c..8ddd4fc 100644
--- a/web/pgadmin/tools/backup/templates/backup/js/backup.js
+++ b/web/pgadmin/tools/backup/templates/backup/js/backup.js
@@ -747,20 +747,20 @@ TODO LIST FOR BACKUP:
                   var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]);
 
                   // Set current database into model
-                  this.view.model.set('database', treeInfo.database.label);
+                  this.view.model.set('database', _.unescape(treeInfo.database.label));
 
                   // We will remove once object tree is implemented
                   // If selected node is Schema then add it in model
                   if(d._type == 'schema') {
                     var schemas = [];
-                    schemas.push(d.label);
+                    schemas.push(_.unescape(d.label));
                     this.view.model.set('schemas', schemas);
                   }
                   // If selected node is Table then add it in model along with
                   // its schema
                   if(d._type == 'table') {
                     this.view.model.set(
-                      'tables', [[treeInfo.schema.label, d.label]]
+                      'tables', [[_.unescape(treeInfo.schema.label), _.unescape(d.label)]]
                     );
                   }
 
diff --git a/web/pgadmin/tools/import_export/__init__.py b/web/pgadmin/tools/import_export/__init__.py
index e1dc061..bfae98b 100644
--- a/web/pgadmin/tools/import_export/__init__.py
+++ b/web/pgadmin/tools/import_export/__init__.py
@@ -254,8 +254,7 @@ def create_import_export_job(sid):
 
     args = [
         '--host', server.host, '--port', str(server.port),
-        '--username', server.username, '--dbname',
-        driver.qtIdent(conn, data['database']),
+        '--username', server.username, '--dbname', data['database'],
         '--command', query
     ]
 
diff --git a/web/pgadmin/tools/import_export/templates/import_export/js/import_export.js b/web/pgadmin/tools/import_export/templates/import_export/js/import_export.js
index f564a0e..0859b16 100644
--- a/web/pgadmin/tools/import_export/templates/import_export/js/import_export.js
+++ b/web/pgadmin/tools/import_export/templates/import_export/js/import_export.js
@@ -400,9 +400,9 @@ define(
                     treeInfo = n.getTreeNodeHierarchy.apply(n, [i])
 
                   this.view.model.set({
-                    'database': treeInfo.database.label,
-                    'schema': treeInfo.schema.label,
-                    'table': treeInfo.table.label
+                    'database': _.unescape(treeInfo.database.label),
+                    'schema': _.unescape(treeInfo.schema.label),
+                    'table': _.unescape(treeInfo.table.label)
                   });
                   var self = this,
                       baseUrl = "{{ url_for('import_export.index') }}" +
diff --git a/web/pgadmin/tools/restore/templates/restore/js/restore.js b/web/pgadmin/tools/restore/templates/restore/js/restore.js
index 2bc35e8..fe21a38 100644
--- a/web/pgadmin/tools/restore/templates/restore/js/restore.js
+++ b/web/pgadmin/tools/restore/templates/restore/js/restore.js
@@ -472,26 +472,26 @@ define([
                   var info = node.getTreeNodeHierarchy.apply(node, [i]),
                       m = this.view.model;
                   // Set current node info into model
-                  m.set('database', info.database.label);
+                  m.set('database', _.unescape(info.database.label));
                   if (!m.get('custom')) {
                     switch (d._type) {
                       case 'schema':
-                        m.set('schemas', d.label);
+                        m.set('schemas', _.unescape(d.label));
                         break;
                       case 'table':
-                        m.set('tables', [info.schema.label, d.label]);
+                        m.set('tables', [_.unescape(info.schema.label), _.unescape(d.label)]);
                         break;
                       case 'function':
-                        m.set('functions', [info.schema.label, d.label]);
+                        m.set('functions', [_.unescape(info.schema.label), _.unescape(d.label)]);
                         break;
                       case 'index':
-                        m.set('indexes', [info.schema.label, d.label]);
+                        m.set('indexes', [_.unescape(info.schema.label), _.unescape(d.label)]);
                         break;
                       case 'trigger':
-                        m.set('triggers', [info.schema.label, d.label]);
+                        m.set('triggers', [_.unescape(info.schema.label), _.unescape(d.label)]);
                         break;
                       case 'trigger_func':
-                        m.set('trigger_funcs', [info.schema.label, d.label]);
+                        m.set('trigger_funcs', [_.unescape(info.schema.label), _.unescape(d.label)]);
                         break;
                     }
                   } else {

-- 
Sent via pgadmin-hackers mailing list ([email-masked])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers

---

From: Khushboo Vashi <[email-masked]>
Date: Fri, 14 Oct 2016 19:01:18 +0530

Hi,

The fix does not include a special case when database name has an equal to
(=) character in it.

Thanks,
Khushboo

---

From: Dave Page <[email-masked]>
Date: Sat, 15 Oct 2016 00:29:06 +0100

Hi

> 2. Required command line arguments were quoted twice

This is not working for me: I tested using Table Export as per Fahar's
instructions. As I'm in desktop mode, the first problem was that we get an
error at line 210 of import_export/__init__.py, because
get_server_directory returned None for the directory. If I fix that, then
the job says it's created, but as far as I can see, nothing else happens.

Secondly, this patch seems to push quoting responsibilty to the front end.
This doesn't seem right, because we might want to use the RESTful APIs for
another purpose in the future, which would mean needing to re-implement
quoting if something else uses an affected API.

Thanks.

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

---

From: Dave Page <[email-masked]>
Date: Sat, 15 Oct 2016 00:47:19 +0100

On Friday, October 14, 2016, Dave Page <[email-masked]> wrote:

> Hi
>
> On Friday, October 14, 2016, Khushboo Vashi <khushboo.vashi@enterprisedb.
> com <javascript:_e(%7B%7D,'cvml',[email-masked]');>>
> wrote:
>
>> Hi,
>>

Oh, oh, oh - 30 minutes later I restarted pgAdmin and got a failed job
message!

Copying table data 'q m g r o c k s.q m g r o c k s ' on database 'q m g r
o c k s' for the server - 'EPAS 9.5 (172.16.254.22:5444)'
Running command:
/Library/PostgreSQL/9.6/bin/psql --host "172.16.254.22" --port "5444"
--username "enterprisedb" --dbname "q m g r o c k s" --command "\copy "q m
g r o c k s"."q m g r o c k s " ("q m g r o c k s") TO
'Users/dpage/foo.csv' DELIMITER ';' CSV QUOTE '"' ESCAPE '''';"

Start time: Fri Oct 14 2016 16:21:06 GMT-0700 (PDT)
Users/dpage/foo.csv: No such file or directory

Looks like it lost the leading / on the path, so that might have been my
hacky fix for the first issue.

---

From: Ashesh Vashi <[email-masked]>
Date: Sat, 15 Oct 2016 09:26:33 +0530

On Sat, Oct 15, 2016 at 4:59 AM, Dave Page <[email-masked]> wrote:

> com> wrote:
hmm..

> Secondly, this patch seems to push quoting responsibilty to the front end.
No - that's not the case, we're using _.escape(..) function on the node's
label to fix the issue of XSS vulnerability on client side.
Hence - during sending back the data, we're using _.unescape(..) function
to return the same data coming sent by the server.

Though - IIRC - we have a original label stored in another variable
'_label', which we can use it instead of unescape it again.

> quoting if something else uses an affected API.
As I explained above, it wont affect the RESTful API.

--
Thanks & Regards,

Ashesh Vashi

---

From: Dave Page <[email-masked]>
Date: Sat, 15 Oct 2016 07:22:06 +0100

On Friday, October 14, 2016, Ashesh Vashi <[email-masked]>
wrote:

> On Sat, Oct 15, 2016 at 4:59 AM, Dave Page <[email-masked]
> <javascript:_e(%7B%7D,'cvml',[email-masked]');>> wrote:
>
>> Hi
>>
>> On Friday, October 14, 2016, Khushboo Vashi <
>> [email-masked]
>> <javascript:_e(%7B%7D,'cvml',[email-masked]');>> wrote:
>>
>>> Hi,
>>>

Yes, but please see my followup message. There's clearly something funky
going on with the process tracking - for whatever reason it didn't pick up
this process until after a restart, and per the bug I escalated earlier
(which I think is essential to fix for 1.1 in a little over a week), it
doesn't always detect completed processes and then keeps re-showing the
alert.

> to return the same data coming sent by the server.

Ahh, OK - I see.

> '_label', which we can use it instead of unescape it again.

Right, as we've done in many other places.

> As I explained above, it wont affect the RESTful API.

Yep. Thanks for setting me straight.

---

From: Khushboo Vashi <[email-masked]>
Date: Thu, 20 Oct 2016 16:26:18 +0530

> doesn't always detect completed processes and then keeps re-showing the

The problem here is that, until we click the "Click for details here" link
and close the another details dialogue, the acknowledgement does not send
to the server. So, it keeps re-showing the alert.

I think, we need to clearly mention the steps on the alertify notifier
itself, so the user can get the idea.

Dave/Ashesh,
Any other suggestion?

> Right, as we've done in many other places.

I have replaced  _. unescape with _label

>
>> This doesn't seem right, because we might want to use the RESTful APIs
>>> for another purpose in the future, which would mean needing to re-implement

---

From: Ashesh Vashi <[email-masked]>
Date: Thu, 20 Oct 2016 16:38:50 +0530

> itself, so the user can get the idea.
We can give a acknowledge link along with 'Click here for details' link to
delete the status, logs, when clicked.
Dave?

>>> No - that's not the case, we're using _.escape(..) function on the
>>> node's label to fix the issue of XSS vulnerability on client side.
>>> Hence - during sending back the data, we're using _.unescape(..)
>>> function to return the same data coming sent by the server.
>>>
>>
>> Ahh, OK - I see.
>>
>>
>>>

---

From: Dave Page <[email-masked]>
Date: Thu, 20 Oct 2016 17:10:23 +0100

On Thu, Oct 20, 2016 at 12:08 PM, Ashesh Vashi
<[email-masked]> wrote:
> On Thu, Oct 20, 2016 at 4:26 PM, Khushboo Vashi
> <[email-masked]> wrote:
>>
>>
>>
>>>>> On Friday, October 14, 2016, Khushboo Vashi
>>>>> <[email-masked]> wrote:
>>>>>>
>>>>>> Hi,
>>>>>>
>>>>> error at line 210 of import_export/__init__.py, because get_server_directory
>>>>> returned None for the directory. If I fix that, then the job says it's
>>>>> created, but as far as I can see, nothing else happens.
>>>>
>>>> hmm..
>>>
>>>
>> and close the another details dialogue, the acknowledgement does not send to
>> the server. So, it keeps re-showing the alert.
>>

Sure, we can do that - but with so many instances being reported,
clearly there's a root cause to fix first.

---

From: Khushboo Vashi <[email-masked]>
Date: Fri, 21 Oct 2016 10:44:21 +0530

Hi,

Please find the attached patch to fix below RMs:

1603:  [Web Based] Export database failed if object contains double quotes
1220:  Backup database is not working with special characters
1221:  Maintenance DB failed if database name contains special characters

In Desktop mode, the import/export didn't work that is also fixed.

Regarding, re-showing the dialogue multiple times, Murtuza is working on
this issue,

Murtuza,
Please also look into this thread, so you can have more idea about the
related bugs.

Thanks,
Khushboo

On Thu, Oct 20, 2016 at 9:40 PM, Dave Page <[email-masked]> wrote:

> get_server_directory

 
 
     try:
         p = BatchProcess(
index a13a31c..9d86386 100644
 
                   // Set current database into model
-                  this.view.model.set('database', treeInfo.database.label);
+                  this.view.model.set('database', treeInfo.database._label);
 
                   // We will remove once object tree is implemented
                   // If selected node is Schema then add it in model
                   if(d._type == 'schema') {
                     var schemas = [];
-                    schemas.push(d.label);
+                    schemas.push(d._label);
                     this.view.model.set('schemas', schemas);
                   }
                   // If selected node is Table then add it in model along with
                   // its schema
                   if(d._type == 'table') {
                     this.view.model.set(
-                      'tables', [[treeInfo.schema.label, d.label]]
+                      'tables', [[treeInfo.schema._label, d._label]]
                     );
                   }
 
diff --git a/web/pgadmin/tools/import_export/__init__.py b/web/pgadmin/tools/import_export/__init__.py
index e1dc061..29c0dd4 100644
--- a/web/pgadmin/tools/import_export/__init__.py
+++ b/web/pgadmin/tools/import_export/__init__.py
@@ -206,8 +206,11 @@ def create_import_export_job(sid):
                 storage_dir = storage_dir.replace('/', '\\')
             data['filename'] = data['filename'].replace('\\', '\\\\')
             data['filename'] = os.path.join(storage_dir, data['filename'].lstrip('/'))
-        else:
+        elif storage_dir:
             data['filename'] = os.path.join(storage_dir, data['filename'].lstrip('/'))
+        else:
+            data['filename'] = data['filename']
+
     else:
         return make_json_response(
             data={'status': False, 'info': 'Please specify a valid file'}
@@ -254,8 +257,7 @@ def create_import_export_job(sid):
 
     args = [
 
index f564a0e..612992c 100644
 
                   this.view.model.set({
-                    'database': treeInfo.database.label,
-                    'schema': treeInfo.schema.label,
-                    'table': treeInfo.table.label
+                    'database': treeInfo.database._label,
+                    'schema': treeInfo.schema._label,
+                    'table': treeInfo.table._label
                   });
                   var self = this,
                       baseUrl = "{{ url_for('import_export.index') }}" +
diff --git a/web/pgadmin/tools/maintenance/__init__.py b/web/pgadmin/tools/maintenance/__init__.py
index 8853c8b..4229192 100644
--- a/web/pgadmin/tools/maintenance/__init__.py
+++ b/web/pgadmin/tools/maintenance/__init__.py
@@ -217,7 +217,7 @@ def create_maintenance_job(sid, did):
     args = [
         '--host', server.host, '--port', str(server.port),
         '--username', server.username, '--dbname',
-        driver.qtIdent(conn, data['database']),
+        data['database'],
         '--command', query
     ]
 
diff --git a/web/pgadmin/tools/maintenance/templates/maintenance/js/maintenance.js b/web/pgadmin/tools/maintenance/templates/maintenance/js/maintenance.js
index 7a856b6..5fcf70c 100644
--- a/web/pgadmin/tools/maintenance/templates/maintenance/js/maintenance.js
+++ b/web/pgadmin/tools/maintenance/templates/maintenance/js/maintenance.js
@@ -295,21 +295,21 @@ define(
                   var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]);
 
                   if (treeInfo.schema != undefined) {
-                    schema = treeInfo.schema.label;
+                    schema = treeInfo.schema._label;
                   }
                   if (treeInfo.table != undefined) {
-                    table = treeInfo.table.label;
+                    table = treeInfo.table._label;
                   }
 
                   if (treeInfo.primary_key != undefined) {
-                    primary_key = treeInfo.primary_key.label;
+                    primary_key = treeInfo.primary_key._label;
                   } else if (treeInfo.unique_constraint != undefined) {
-                    unique_constraint = treeInfo.unique_constraint.label;
+                    unique_constraint = treeInfo.unique_constraint._label;
                   } else if (treeInfo.index != undefined) {
-                    index = treeInfo.index.label;
+                    index = treeInfo.index._label;
                   }
 
-                  this.view.model.set({'database': treeInfo.database.label,
+                  this.view.model.set({'database': treeInfo.database._label,
                                       'schema': schema,
                                       'table': table,
                                       'primary_key': primary_key,
diff --git a/web/pgadmin/tools/restore/templates/restore/js/restore.js b/web/pgadmin/tools/restore/templates/restore/js/restore.js
index 2bc35e8..7064fa3 100644
--- a/web/pgadmin/tools/restore/templates/restore/js/restore.js
+++ b/web/pgadmin/tools/restore/templates/restore/js/restore.js
@@ -472,26 +472,26 @@ define([
                   var info = node.getTreeNodeHierarchy.apply(node, [i]),
                       m = this.view.model;
                   // Set current node info into model
-                  m.set('database', info.database.label);
+                  m.set('database', info.database._label);
                   if (!m.get('custom')) {
                     switch (d._type) {
                       case 'schema':
-                        m.set('schemas', d.label);
+                        m.set('schemas', d._label);
                         break;
                       case 'table':
-                        m.set('tables', [info.schema.label, d.label]);
+                        m.set('tables', [info.schema._label, d._label]);
                         break;
                       case 'function':
-                        m.set('functions', [info.schema.label, d.label]);
+                        m.set('functions', [info.schema._label, d._label]);
                         break;
                       case 'index':
-                        m.set('indexes', [info.schema.label, d.label]);
+                        m.set('indexes', [info.schema._label, d._label]);
                         break;
                       case 'trigger':
-                        m.set('triggers', [info.schema.label, d.label]);
+                        m.set('triggers', [info.schema._label, d._label]);
                         break;
                       case 'trigger_func':
-                        m.set('trigger_funcs', [info.schema.label, d.label]);
+                        m.set('trigger_funcs', [info.schema._label, d._label]);
                         break;
                     }
                   } else {

---

From: Dave Page <[email-masked]>
Date: Fri, 21 Oct 2016 14:57:42 +0100

Thanks, patch applied.