Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.

Commit 9e3c71e

Browse files
committed
webui: Do not use base64 encoding for queries and visualisations
Do not convert SQL queries on the execute and visualise pages to base64 when sending them to the server.
1 parent aac54bf commit 9e3c71e

File tree

9 files changed

+151
-97
lines changed

9 files changed

+151
-97
lines changed

api/handlers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ func executeHandler(w http.ResponseWriter, r *http.Request) {
663663
// Grab the incoming SQLite query
664664
rawInput := r.FormValue("sql")
665665
var sql string
666-
sql, err = com.CheckUnicode(rawInput)
666+
sql, err = com.CheckUnicode(rawInput, true)
667667
if err != nil {
668668
jsonErr(w, err.Error(), http.StatusBadRequest)
669669
return
@@ -927,7 +927,7 @@ func queryHandler(w http.ResponseWriter, r *http.Request) {
927927

928928
// Grab the incoming SQLite query
929929
rawInput := r.FormValue("sql")
930-
query, err := com.CheckUnicode(rawInput)
930+
query, err := com.CheckUnicode(rawInput, true)
931931
if err != nil {
932932
jsonErr(w, err.Error(), http.StatusBadRequest)
933933
return

common/userinput.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,25 @@ func CheckAPIKey(apiKey string) (err error) {
2424
}
2525

2626
// CheckUnicode checks if a given string is unicode, and safe for using in SQLite queries (eg no SQLite control characters)
27-
func CheckUnicode(rawInput string) (str string, err error) {
27+
func CheckUnicode(rawInput string, decodeBase64 bool) (str string, err error) {
2828
var decoded []byte
29-
decoded, err = base64.StdEncoding.DecodeString(rawInput)
30-
if err != nil {
31-
// When base64 decoding fails, automatically try again with base64url formats instead
32-
var err2 error // We use err2, to not overwrite the initial error message
33-
decoded, err2 = base64.URLEncoding.DecodeString(rawInput)
34-
if err2 != nil {
35-
// Try base64URL with no padding character(s) this time
36-
decoded, err2 = base64.RawURLEncoding.DecodeString(rawInput)
29+
if decodeBase64 {
30+
decoded, err = base64.StdEncoding.DecodeString(rawInput)
31+
if err != nil {
32+
// When base64 decoding fails, automatically try again with base64url formats instead
33+
var err2 error // We use err2, to not overwrite the initial error message
34+
decoded, err2 = base64.URLEncoding.DecodeString(rawInput)
3735
if err2 != nil {
38-
// Nope. Seems like a genuine decoding problem
39-
return
36+
// Try base64URL with no padding character(s) this time
37+
decoded, err2 = base64.RawURLEncoding.DecodeString(rawInput)
38+
if err2 != nil {
39+
// Nope. Seems like a genuine decoding problem
40+
return
41+
}
4042
}
4143
}
44+
} else {
45+
decoded = []byte(rawInput)
4246
}
4347

4448
// Ensure the decoded string is valid UTF-8

common/validate.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,3 +374,12 @@ func ValidateUserEmail(user, email string) error {
374374
}
375375
return nil
376376
}
377+
378+
// ValidateVisualisationName validates the provided name of a saved visualisation query
379+
func ValidateVisualisationName(name string) error {
380+
err := Validate.Var(name, "required,visname,min=1,max=63")
381+
if err != nil {
382+
return err
383+
}
384+
return nil
385+
}

webui/execute.go

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"encoding/json"
55
"fmt"
6+
"io"
67
"log"
78
"net/http"
89
"strings"
@@ -235,27 +236,37 @@ func execLiveSQL(w http.ResponseWriter, r *http.Request) {
235236
}
236237

237238
// Retrieve user and database info
238-
var dbOwner, dbName string
239-
dbOwner, dbName, _, err = com.GetODC(2, r) // 2 = Ignore "/x/execlivesql/" at the start of the URL
239+
dbOwner, dbName, _, err := com.GetODC(2, r) // 2 = Ignore "/x/execlivesql/" at the start of the URL
240240
if err != nil {
241241
w.WriteHeader(http.StatusBadRequest)
242242
fmt.Fprint(w, err)
243243
return
244244
}
245245

246246
// Grab the incoming SQLite query
247-
rawInput := r.FormValue("sql")
248-
var sql string
249-
sql, err = com.CheckUnicode(rawInput)
247+
bodyData, err := io.ReadAll(r.Body)
248+
if err != nil {
249+
w.WriteHeader(http.StatusBadRequest)
250+
fmt.Fprint(w, err)
251+
return
252+
}
253+
var data ExecuteSqlRequest
254+
err = json.Unmarshal([]byte(bodyData), &data)
255+
if err != nil {
256+
w.WriteHeader(http.StatusBadRequest)
257+
fmt.Fprint(w, err)
258+
return
259+
}
260+
261+
sql, err := com.CheckUnicode(data.Sql, false)
250262
if err != nil {
251263
w.WriteHeader(http.StatusBadRequest)
252264
fmt.Fprint(w, err)
253265
return
254266
}
255267

256268
// Check if the requested database exists
257-
var exists bool
258-
exists, err = com.CheckDBPermissions(loggedInUser, dbOwner, dbName, true)
269+
exists, err := com.CheckDBPermissions(loggedInUser, dbOwner, dbName, true)
259270
if err != nil {
260271
w.WriteHeader(http.StatusInternalServerError)
261272
fmt.Fprint(w, err)
@@ -268,9 +279,7 @@ func execLiveSQL(w http.ResponseWriter, r *http.Request) {
268279
}
269280

270281
// Make sure this is a live database
271-
var isLive bool
272-
var liveNode string
273-
isLive, liveNode, err = com.CheckDBLive(dbOwner, dbName)
282+
isLive, liveNode, err := com.CheckDBLive(dbOwner, dbName)
274283
if err != nil {
275284
w.WriteHeader(http.StatusInternalServerError)
276285
fmt.Fprint(w, err)
@@ -283,9 +292,8 @@ func execLiveSQL(w http.ResponseWriter, r *http.Request) {
283292
}
284293

285294
// Send the SQL execution request to our AMQP backend
286-
var rowsChanged int
287295
var z interface{}
288-
rowsChanged, err = com.LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql)
296+
rowsChanged, err := com.LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql)
289297
if err != nil {
290298
if !strings.HasPrefix(err.Error(), "don't use exec with") {
291299
log.Println(err)
@@ -398,34 +406,38 @@ func execSave(w http.ResponseWriter, r *http.Request) {
398406
return
399407
}
400408

401-
// SQL statement provided by the user
402-
rawSQL := r.FormValue("sql")
403-
404-
// Initial sanity check of the SQL statements' name
405-
input := com.VisGetFields{ // Reuse the validation rules for saved visualisation names
406-
VisName: r.FormValue("sqlname"),
409+
// SQL statement and name provided by the user
410+
bodyData, err := io.ReadAll(r.Body)
411+
if err != nil {
412+
w.WriteHeader(http.StatusBadRequest)
413+
fmt.Fprint(w, err)
414+
return
407415
}
408-
err = com.Validate.Struct(input)
416+
var data SaveSqlRequest
417+
err = json.Unmarshal([]byte(bodyData), &data)
409418
if err != nil {
410-
log.Printf("Input validation error for execSave(): %s", err)
411419
w.WriteHeader(http.StatusBadRequest)
412-
fmt.Fprintf(w, "Error when validating input: %s", err)
420+
fmt.Fprint(w, err)
413421
return
414422
}
415-
sqlName := input.VisName
416423

417-
// Ensure minimum viable parameters are present
418-
if sqlName == "" || rawSQL == "" {
424+
decodedStr, err := com.CheckUnicode(data.Sql, false)
425+
if err != nil {
419426
w.WriteHeader(http.StatusBadRequest)
427+
fmt.Fprint(w, err)
420428
return
421429
}
422430

423-
// Make sure the incoming SQLite query is "safe"
424-
var decodedStr string
425-
decodedStr, err = com.CheckUnicode(rawSQL)
431+
err = com.ValidateVisualisationName(data.SqlName)
426432
if err != nil {
427433
w.WriteHeader(http.StatusBadRequest)
428-
fmt.Fprint(w, err.Error())
434+
fmt.Fprint(w, err)
435+
return
436+
}
437+
438+
// Ensure minimum viable parameters are present
439+
if data.SqlName == "" || decodedStr == "" {
440+
w.WriteHeader(http.StatusBadRequest)
429441
return
430442
}
431443

@@ -456,8 +468,7 @@ func execSave(w http.ResponseWriter, r *http.Request) {
456468
}
457469

458470
// Make sure the logged in user has the permissions to proceed
459-
var allowed bool
460-
allowed, err = com.CheckDBPermissions(loggedInUser, dbOwner, dbName, true)
471+
allowed, err := com.CheckDBPermissions(loggedInUser, dbOwner, dbName, true)
461472
if err != nil {
462473
w.WriteHeader(http.StatusInternalServerError)
463474
fmt.Fprint(w, err)
@@ -470,8 +481,7 @@ func execSave(w http.ResponseWriter, r *http.Request) {
470481
}
471482

472483
// Ensure this is a live database
473-
var isLive bool
474-
isLive, _, err = com.CheckDBLive(dbOwner, dbName)
484+
isLive, _, err := com.CheckDBLive(dbOwner, dbName)
475485
if err != nil {
476486
w.WriteHeader(http.StatusInternalServerError)
477487
fmt.Fprint(w, err)
@@ -484,10 +494,10 @@ func execSave(w http.ResponseWriter, r *http.Request) {
484494
}
485495

486496
// Save the SQL statement
487-
err = com.LiveExecuteSQLSave(dbOwner, sqlName, decodedStr)
497+
err = com.LiveExecuteSQLSave(dbOwner, data.SqlName, decodedStr)
488498
if err != nil {
489499
log.Printf("Error occurred when saving SQL statement '%s' for' '%s/%s': %s",
490-
com.SanitiseLogString(sqlName), com.SanitiseLogString(dbOwner), com.SanitiseLogString(dbName), err.Error())
500+
com.SanitiseLogString(data.SqlName), com.SanitiseLogString(dbOwner), com.SanitiseLogString(dbName), err.Error())
491501
w.WriteHeader(http.StatusInternalServerError)
492502
return
493503
}

webui/js/local.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
// base64url encode a string
2-
function base64url(input) {
3-
let encoded = window.btoa(input);
4-
encoded = encoded.replace(/=+$/, '');
5-
encoded = encoded.replace(/\+/g, '-');
6-
encoded = encoded.replace(/\//g, '_');
7-
return encoded
8-
}
9-
101
// Construct a timestamp string for use in user messages
112
function nowString() {
123
// Construct a timestamp for the success message

webui/templates/execute.html

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,6 @@ <h4 style="color: {{ statusMessageColour }};" data-cy="statusmsg">&nbsp;{{ statu
8181
[[ template "script_db_header" . ]]
8282
[[ template "footer" . ]]
8383
<script>
84-
// base64url encode a string
85-
function base64url(input) {
86-
let encoded = window.btoa(input);
87-
encoded = encoded.replace(/=+$/, '');
88-
encoded = encoded.replace(/\+/g, '-');
89-
encoded = encoded.replace(/\//g, '_');
90-
return encoded
91-
}
92-
9384
// Pre-filled table row data
9485
let dataReceived = false;
9586

@@ -286,8 +277,12 @@ <h4 style="color: {{ statusMessageColour }};" data-cy="statusmsg">&nbsp;{{ statu
286277
if (!$scope.checkSQL()) return;
287278

288279
// Send the SQL string to the backend
289-
let encoded = base64url(userSQL);
290-
$http.get("/x/execlivesql/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?commit=[[ .DB.Info.CommitID ]]&sql="+encoded).then(
280+
$http({
281+
url: "/x/execlivesql/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]",
282+
method: "post",
283+
data: {sql: userSQL},
284+
headers: {'Content-Type': 'application/json'}
285+
}).then(
291286
function success(response) {
292287
// We can potentially receive either "Execute SQL" or Query responses here
293288
if ("rows_changed" in response.data) {
@@ -356,10 +351,13 @@ <h4 style="color: {{ statusMessageColour }};" data-cy="statusmsg">&nbsp;{{ statu
356351
if (!$scope.checkSQL()) return;
357352

358353
// Save it
359-
let name = document.getElementById("savename").value;
360-
let args = "sqlname=" + name;
361-
args += "&sql=" + base64url(userSQL);
362-
$http.get("/x/execsave/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?commit=[[ .DB.Info.CommitID ]]&"+args).then(
354+
const name = document.getElementById("savename").value;
355+
$http({
356+
url: "/x/execsave/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]",
357+
method: "post",
358+
data: {sql_name: name, sql: userSQL},
359+
headers: {'Content-Type': 'application/json'}
360+
}).then(
363361
function success(response) {
364362
$scope.statusMessageColour = "green";
365363
$scope.statusMessage = nowString() + "SQL statement '" + name + "' saved";

webui/templates/visualise.html

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -595,8 +595,12 @@ <h4 style="color: {{ statusMessageColour }};" data-cy="statusmsg">&nbsp;{{ statu
595595
if (!$scope.checkSQL()) return;
596596

597597
// Generate the csv
598-
let encoded = base64url(userSQL);
599-
$http.get("/x/visdlresults/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?commit=[[ .DB.Info.CommitID ]]&sql="+encoded).then(
598+
$http({
599+
url: "/x/visdlresults/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?commit=[[ .DB.Info.CommitID ]]",
600+
method: "post",
601+
data: {sql: userSQL},
602+
headers: {'Content-Type': 'application/json'}
603+
}).then(
600604
function success(response) {
601605
// Clear any existing status message
602606
$scope.statusMessage = "";
@@ -630,8 +634,12 @@ <h4 style="color: {{ statusMessageColour }};" data-cy="statusmsg">&nbsp;{{ statu
630634
if (!$scope.checkSQL()) return;
631635

632636
// Send the SQL string to the backend
633-
let encoded = base64url(userSQL);
634-
$http.get("/x/execsql/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?commit=[[ .DB.Info.CommitID ]]&sql="+encoded).then(
637+
$http({
638+
url: "/x/execsql/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?commit=[[ .DB.Info.CommitID ]]",
639+
method: "post",
640+
data: {sql: userSQL},
641+
headers: {'Content-Type': 'application/json'}
642+
}).then(
635643
function success(response) {
636644
// Give a useful "success" status message
637645
$scope.statusMessageColour = "green";
@@ -707,7 +715,6 @@ <h4 style="color: {{ statusMessageColour }};" data-cy="statusmsg">&nbsp;{{ statu
707715
let args = "xaxis=" + $scope.XAxisColumn;
708716
args += "&yaxis=" + $scope.YAxisColumn;
709717
args += "&visname=" + name;
710-
args += "&sql=" + base64url(userSQL);
711718
switch ($scope.ChartType) {
712719
case "Horizontal bar chart":
713720
args += "&charttype=hbc";
@@ -729,7 +736,12 @@ <h4 style="color: {{ statusMessageColour }};" data-cy="statusmsg">&nbsp;{{ statu
729736
args += "&showxlabel=" + $scope.radioXAxisShowLabels;
730737
args += "&showylabel=" + $scope.radioYAxisShowLabels;
731738
}
732-
$http.get("/x/vissave/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?commit=[[ .DB.Info.CommitID ]]&"+args).then(
739+
$http({
740+
url: "/x/vissave/[[ .DB.Info.Owner ]]/[[ .DB.Info.Database ]]?commit=[[ .DB.Info.CommitID ]]&" + args,
741+
method: "post",
742+
data: {sql: userSQL},
743+
headers: {'Content-Type': 'application/json'}
744+
}).then(
733745
function success(response) {
734746
$scope.statusMessageColour = "green";
735747
$scope.statusMessage = nowString() + "Visualisation '" + name + "' saved";

webui/types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,12 @@ type UpdateDataRequest struct {
2727
Table string `json:"table"`
2828
Data []UpdateDataRequestRow `json:"data"`
2929
}
30+
31+
type ExecuteSqlRequest struct {
32+
Sql string `json:"sql"`
33+
}
34+
35+
type SaveSqlRequest struct {
36+
Sql string `json:"sql"`
37+
SqlName string `json:"sql_name"`
38+
}

0 commit comments

Comments
 (0)