Return HTTP 404 instead of 500 for invalid UUID values in URL
Since most of PL/pgSQL functions accept a `uuid` domain, we get an error if the value is not valid, forcing us to return an HTTP 500, as we can not detect that the error was due to that. Instead, i now validate that the slug is indeed a valid UUID before attempting to send it to the database, returning the correct HTTP error code and avoiding useless calls to the database. I based the validation function of Parse() from Google’s uuid package[0] because this function is an order or magnitude faster in benchmarks: goos: linux goarch: amd64 pkg: dev.tandem.ws/tandem/numerus/pkg cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz BenchmarkValidUuid-4 36946050 29.37 ns/op BenchmarkValidUuid_Re-4 3633169 306.70 ns/op The regular expression used for the benchmark was: var re = regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$") And the input parameter for both functions was the following valid UUID, because most of the time the passed UUID will be valid: "f47ac10b-58cc-0372-8567-0e02b2c3d479" I did not use the uuid package, even though it is in Debian’s repository, because i only need to check whether the value is valid, not convert it to a byte array. As far as i know, that package can not do that. [0]: tandem/tandem#40
This commit is contained in:
parent
5e8bed8452
commit
3d3221e202
|
@ -53,6 +53,10 @@ func GetContactForm(w http.ResponseWriter, r *http.Request, params httprouter.Pa
|
||||||
mustRenderNewContactForm(w, r, form)
|
mustRenderNewContactForm(w, r, form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
|
@ -120,7 +124,12 @@ func HandleUpdateContact(w http.ResponseWriter, r *http.Request, params httprout
|
||||||
mustRenderEditContactForm(w, r, params[0].Value, form)
|
mustRenderEditContactForm(w, r, params[0].Value, form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
slug := conn.MustGetText(r.Context(), "", "select edit_contact($1, $2, $3, $4, $5, $6, $7, $8, $9)", params[0].Value, form.Name, form.Phone, form.Email, form.Web, form.TaxDetails(), form.IBAN, form.BIC, form.Tags)
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slug = conn.MustGetText(r.Context(), "", "select edit_contact($1, $2, $3, $4, $5, $6, $7, $8, $9)", slug, form.Name, form.Phone, form.Email, form.Web, form.TaxDetails(), form.IBAN, form.BIC, form.Tags)
|
||||||
if slug == "" {
|
if slug == "" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
@ -486,6 +495,10 @@ func ServeEditContactTags(w http.ResponseWriter, r *http.Request, params httprou
|
||||||
locale := getLocale(r)
|
locale := getLocale(r)
|
||||||
company := getCompany(r)
|
company := getCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
form := newTagsForm(companyURI(company, "/contacts/"+slug+"/tags"), slug, locale)
|
form := newTagsForm(companyURI(company, "/contacts/"+slug+"/tags"), slug, locale)
|
||||||
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from contact where slug = $1`, form.Slug).Scan(form.Tags)) {
|
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from contact where slug = $1`, form.Slug).Scan(form.Tags)) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -499,6 +512,10 @@ func HandleUpdateContactTags(w http.ResponseWriter, r *http.Request, params http
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
company := getCompany(r)
|
company := getCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
form := newTagsForm(companyURI(company, "/contacts/"+slug+"/tags/edit"), slug, locale)
|
form := newTagsForm(companyURI(company, "/contacts/"+slug+"/tags/edit"), slug, locale)
|
||||||
if err := form.Parse(r); err != nil {
|
if err := form.Parse(r); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
|
|
@ -155,6 +155,10 @@ func ServeExpenseForm(w http.ResponseWriter, r *http.Request, params httprouter.
|
||||||
mustRenderNewExpenseForm(w, r, form)
|
mustRenderNewExpenseForm(w, r, form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
|
@ -364,14 +368,19 @@ func HandleUpdateExpense(w http.ResponseWriter, r *http.Request, params httprout
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
if r.FormValue("quick") == "status" {
|
if r.FormValue("quick") == "status" {
|
||||||
slug := conn.MustGetText(r.Context(), "", "update expense set expense_status = $1 where slug = $2 returning slug", form.ExpenseStatus, params[0].Value)
|
slug = conn.MustGetText(r.Context(), "", "update expense set expense_status = $1 where slug = $2 returning slug", form.ExpenseStatus, slug)
|
||||||
if slug == "" {
|
if slug == "" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
htmxRedirect(w, r, companyURI(mustGetCompany(r), "/expenses"))
|
htmxRedirect(w, r, companyURI(mustGetCompany(r), "/expenses"))
|
||||||
} else {
|
} else {
|
||||||
slug := params[0].Value
|
|
||||||
if !form.Validate() {
|
if !form.Validate() {
|
||||||
if !IsHTMxRequest(r) {
|
if !IsHTMxRequest(r) {
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||||
|
@ -520,6 +529,10 @@ func ServeEditExpenseTags(w http.ResponseWriter, r *http.Request, params httprou
|
||||||
locale := getLocale(r)
|
locale := getLocale(r)
|
||||||
company := getCompany(r)
|
company := getCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
form := newTagsForm(companyURI(company, "/expenses/"+slug+"/tags"), slug, locale)
|
form := newTagsForm(companyURI(company, "/expenses/"+slug+"/tags"), slug, locale)
|
||||||
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from expense where slug = $1`, form.Slug).Scan(form.Tags)) {
|
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from expense where slug = $1`, form.Slug).Scan(form.Tags)) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -533,6 +546,10 @@ func HandleUpdateExpenseTags(w http.ResponseWriter, r *http.Request, params http
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
company := getCompany(r)
|
company := getCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
form := newTagsForm(companyURI(company, "/expenses/"+slug+"/tags/edit"), slug, locale)
|
form := newTagsForm(companyURI(company, "/expenses/"+slug+"/tags/edit"), slug, locale)
|
||||||
if err := form.Parse(r); err != nil {
|
if err := form.Parse(r); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
@ -544,12 +561,17 @@ func HandleUpdateExpenseTags(w http.ResponseWriter, r *http.Request, params http
|
||||||
}
|
}
|
||||||
if conn.MustGetText(r.Context(), "", "update expense set tags = $1 where slug = $2 returning slug", form.Tags, form.Slug) == "" {
|
if conn.MustGetText(r.Context(), "", "update expense set tags = $1 where slug = $2 returning slug", form.Tags, form.Slug) == "" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
mustRenderStandaloneTemplate(w, r, "tags/view.gohtml", form)
|
mustRenderStandaloneTemplate(w, r, "tags/view.gohtml", form)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServeExpenseAttachment(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
func ServeExpenseAttachment(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
var contentType string
|
var contentType string
|
||||||
var content []byte
|
var content []byte
|
||||||
|
@ -571,6 +593,10 @@ func ServeExpenseAttachment(w http.ResponseWriter, r *http.Request, params httpr
|
||||||
|
|
||||||
func HandleEditExpenseAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
func HandleEditExpenseAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
actionUri := fmt.Sprintf("/invoices/%s/edit", slug)
|
actionUri := fmt.Sprintf("/invoices/%s/edit", slug)
|
||||||
handleExpenseAction(w, r, actionUri, func(w http.ResponseWriter, r *http.Request, form *expenseForm) {
|
handleExpenseAction(w, r, actionUri, func(w http.ResponseWriter, r *http.Request, form *expenseForm) {
|
||||||
mustRenderEditExpenseForm(w, r, slug, form)
|
mustRenderEditExpenseForm(w, r, slug, form)
|
||||||
|
|
|
@ -257,10 +257,10 @@ func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Para
|
||||||
case "new":
|
case "new":
|
||||||
locale := getLocale(r)
|
locale := getLocale(r)
|
||||||
form := newInvoiceForm(r.Context(), conn, locale, company)
|
form := newInvoiceForm(r.Context(), conn, locale, company)
|
||||||
if invoiceToDuplicate := r.URL.Query().Get("duplicate"); invoiceToDuplicate != "" {
|
if invoiceToDuplicate := r.URL.Query().Get("duplicate"); ValidUuid(invoiceToDuplicate) {
|
||||||
form.MustFillFromDatabase(r.Context(), conn, invoiceToDuplicate)
|
form.MustFillFromDatabase(r.Context(), conn, invoiceToDuplicate)
|
||||||
form.InvoiceStatus.Selected = []string{"created"}
|
form.InvoiceStatus.Selected = []string{"created"}
|
||||||
} else if quoteToInvoice := r.URL.Query().Get("quote"); quoteToInvoice != "" {
|
} else if quoteToInvoice := r.URL.Query().Get("quote"); ValidUuid(quoteToInvoice) {
|
||||||
form.MustFillFromQuote(r.Context(), conn, quoteToInvoice)
|
form.MustFillFromQuote(r.Context(), conn, quoteToInvoice)
|
||||||
}
|
}
|
||||||
form.Date.Val = time.Now().Format("2006-01-02")
|
form.Date.Val = time.Now().Format("2006-01-02")
|
||||||
|
@ -289,6 +289,10 @@ func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Para
|
||||||
pdf = true
|
pdf = true
|
||||||
slug = slug[:len(slug)-len(".pdf")]
|
slug = slug[:len(slug)-len(".pdf")]
|
||||||
}
|
}
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
inv := mustGetInvoice(r.Context(), conn, company, slug)
|
inv := mustGetInvoice(r.Context(), conn, company, slug)
|
||||||
if inv == nil {
|
if inv == nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -1151,14 +1155,19 @@ func HandleUpdateInvoice(w http.ResponseWriter, r *http.Request, params httprout
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
if r.FormValue("quick") == "status" {
|
if r.FormValue("quick") == "status" {
|
||||||
slug := conn.MustGetText(r.Context(), "", "update invoice set invoice_status = $1 where slug = $2 returning slug", form.InvoiceStatus, params[0].Value)
|
slug = conn.MustGetText(r.Context(), "", "update invoice set invoice_status = $1 where slug = $2 returning slug", form.InvoiceStatus, slug)
|
||||||
if slug == "" {
|
if slug == "" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
htmxRedirect(w, r, companyURI(mustGetCompany(r), "/invoices"))
|
htmxRedirect(w, r, companyURI(mustGetCompany(r), "/invoices"))
|
||||||
} else {
|
} else {
|
||||||
slug := params[0].Value
|
|
||||||
if !form.Validate() {
|
if !form.Validate() {
|
||||||
if !IsHTMxRequest(r) {
|
if !IsHTMxRequest(r) {
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||||
|
@ -1194,6 +1203,10 @@ func ServeEditInvoice(w http.ResponseWriter, r *http.Request, params httprouter.
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
company := mustGetCompany(r)
|
company := mustGetCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
locale := getLocale(r)
|
locale := getLocale(r)
|
||||||
form := newInvoiceForm(r.Context(), conn, locale, company)
|
form := newInvoiceForm(r.Context(), conn, locale, company)
|
||||||
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
||||||
|
@ -1225,6 +1238,10 @@ func mustRenderEditInvoiceForm(w http.ResponseWriter, r *http.Request, slug stri
|
||||||
|
|
||||||
func HandleEditInvoiceAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
func HandleEditInvoiceAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
actionUri := fmt.Sprintf("/invoices/%s/edit", slug)
|
actionUri := fmt.Sprintf("/invoices/%s/edit", slug)
|
||||||
handleInvoiceAction(w, r, actionUri, func(w http.ResponseWriter, r *http.Request, form *invoiceForm) {
|
handleInvoiceAction(w, r, actionUri, func(w http.ResponseWriter, r *http.Request, form *invoiceForm) {
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
|
@ -1319,6 +1336,10 @@ func ServeEditInvoiceTags(w http.ResponseWriter, r *http.Request, params httprou
|
||||||
locale := getLocale(r)
|
locale := getLocale(r)
|
||||||
company := getCompany(r)
|
company := getCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
form := newTagsForm(companyURI(company, "/invoices/"+slug+"/tags"), slug, locale)
|
form := newTagsForm(companyURI(company, "/invoices/"+slug+"/tags"), slug, locale)
|
||||||
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from invoice where slug = $1`, form.Slug).Scan(form.Tags)) {
|
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from invoice where slug = $1`, form.Slug).Scan(form.Tags)) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -1332,6 +1353,10 @@ func HandleUpdateInvoiceTags(w http.ResponseWriter, r *http.Request, params http
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
company := getCompany(r)
|
company := getCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
form := newTagsForm(companyURI(company, "/invoices/"+slug+"/tags/edit"), slug, locale)
|
form := newTagsForm(companyURI(company, "/invoices/"+slug+"/tags/edit"), slug, locale)
|
||||||
if err := form.Parse(r); err != nil {
|
if err := form.Parse(r); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
@ -1349,6 +1374,10 @@ func HandleUpdateInvoiceTags(w http.ResponseWriter, r *http.Request, params http
|
||||||
|
|
||||||
func ServeInvoiceAttachment(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
func ServeInvoiceAttachment(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
var contentType string
|
var contentType string
|
||||||
var content []byte
|
var content []byte
|
||||||
|
|
|
@ -50,6 +50,10 @@ func GetProductForm(w http.ResponseWriter, r *http.Request, params httprouter.Pa
|
||||||
mustRenderNewProductForm(w, r, form)
|
mustRenderNewProductForm(w, r, form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
|
@ -136,6 +140,10 @@ func HandleUpdateProduct(w http.ResponseWriter, r *http.Request, params httprout
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
if !form.Validate() {
|
if !form.Validate() {
|
||||||
if !IsHTMxRequest(r) {
|
if !IsHTMxRequest(r) {
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||||
|
@ -363,6 +371,10 @@ func ServeEditProductTags(w http.ResponseWriter, r *http.Request, params httprou
|
||||||
locale := getLocale(r)
|
locale := getLocale(r)
|
||||||
company := getCompany(r)
|
company := getCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
form := newTagsForm(companyURI(company, "/products/"+slug+"/tags"), slug, locale)
|
form := newTagsForm(companyURI(company, "/products/"+slug+"/tags"), slug, locale)
|
||||||
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from product where slug = $1`, form.Slug).Scan(form.Tags)) {
|
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from product where slug = $1`, form.Slug).Scan(form.Tags)) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -376,6 +388,10 @@ func HandleUpdateProductTags(w http.ResponseWriter, r *http.Request, params http
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
company := getCompany(r)
|
company := getCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
form := newTagsForm(companyURI(company, "/products/"+slug+"/tags/edit"), slug, locale)
|
form := newTagsForm(companyURI(company, "/products/"+slug+"/tags/edit"), slug, locale)
|
||||||
if err := form.Parse(r); err != nil {
|
if err := form.Parse(r); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
|
31
pkg/quote.go
31
pkg/quote.go
|
@ -258,7 +258,7 @@ func ServeQuote(w http.ResponseWriter, r *http.Request, params httprouter.Params
|
||||||
case "new":
|
case "new":
|
||||||
locale := getLocale(r)
|
locale := getLocale(r)
|
||||||
form := newQuoteForm(r.Context(), conn, locale, company)
|
form := newQuoteForm(r.Context(), conn, locale, company)
|
||||||
if quoteToDuplicate := r.URL.Query().Get("duplicate"); quoteToDuplicate != "" {
|
if quoteToDuplicate := r.URL.Query().Get("duplicate"); ValidUuid(quoteToDuplicate) {
|
||||||
form.MustFillFromDatabase(r.Context(), conn, quoteToDuplicate)
|
form.MustFillFromDatabase(r.Context(), conn, quoteToDuplicate)
|
||||||
form.QuoteStatus.Selected = []string{"created"}
|
form.QuoteStatus.Selected = []string{"created"}
|
||||||
}
|
}
|
||||||
|
@ -288,6 +288,10 @@ func ServeQuote(w http.ResponseWriter, r *http.Request, params httprouter.Params
|
||||||
pdf = true
|
pdf = true
|
||||||
slug = slug[:len(slug)-len(".pdf")]
|
slug = slug[:len(slug)-len(".pdf")]
|
||||||
}
|
}
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
quo := mustGetQuote(r.Context(), conn, company, slug)
|
quo := mustGetQuote(r.Context(), conn, company, slug)
|
||||||
if quo == nil {
|
if quo == nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -1036,14 +1040,19 @@ func HandleUpdateQuote(w http.ResponseWriter, r *http.Request, params httprouter
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
if r.FormValue("quick") == "status" {
|
if r.FormValue("quick") == "status" {
|
||||||
slug := conn.MustGetText(r.Context(), "", "update quote set quote_status = $1 where slug = $2 returning slug", form.QuoteStatus, params[0].Value)
|
slug = conn.MustGetText(r.Context(), "", "update quote set quote_status = $1 where slug = $2 returning slug", form.QuoteStatus, slug)
|
||||||
if slug == "" {
|
if slug == "" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
htmxRedirect(w, r, companyURI(mustGetCompany(r), "/quotes"))
|
htmxRedirect(w, r, companyURI(mustGetCompany(r), "/quotes"))
|
||||||
} else {
|
} else {
|
||||||
slug := params[0].Value
|
|
||||||
if !form.Validate() {
|
if !form.Validate() {
|
||||||
if !IsHTMxRequest(r) {
|
if !IsHTMxRequest(r) {
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||||
|
@ -1064,6 +1073,10 @@ func ServeEditQuote(w http.ResponseWriter, r *http.Request, params httprouter.Pa
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
company := mustGetCompany(r)
|
company := mustGetCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
locale := getLocale(r)
|
locale := getLocale(r)
|
||||||
form := newQuoteForm(r.Context(), conn, locale, company)
|
form := newQuoteForm(r.Context(), conn, locale, company)
|
||||||
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
|
||||||
|
@ -1095,6 +1108,10 @@ func mustRenderEditQuoteForm(w http.ResponseWriter, r *http.Request, slug string
|
||||||
|
|
||||||
func HandleEditQuoteAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
func HandleEditQuoteAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
actionUri := fmt.Sprintf("/quotes/%s/edit", slug)
|
actionUri := fmt.Sprintf("/quotes/%s/edit", slug)
|
||||||
handleQuoteAction(w, r, actionUri, func(w http.ResponseWriter, r *http.Request, form *quoteForm) {
|
handleQuoteAction(w, r, actionUri, func(w http.ResponseWriter, r *http.Request, form *quoteForm) {
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
|
@ -1164,6 +1181,10 @@ func ServeEditQuoteTags(w http.ResponseWriter, r *http.Request, params httproute
|
||||||
locale := getLocale(r)
|
locale := getLocale(r)
|
||||||
company := getCompany(r)
|
company := getCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
form := newTagsForm(companyURI(company, "/quotes/"+slug+"/tags"), slug, locale)
|
form := newTagsForm(companyURI(company, "/quotes/"+slug+"/tags"), slug, locale)
|
||||||
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from quote where slug = $1`, form.Slug).Scan(form.Tags)) {
|
if notFoundErrorOrPanic(conn.QueryRow(r.Context(), `select tags from quote where slug = $1`, form.Slug).Scan(form.Tags)) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -1177,6 +1198,10 @@ func HandleUpdateQuoteTags(w http.ResponseWriter, r *http.Request, params httpro
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
company := getCompany(r)
|
company := getCompany(r)
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
|
if !ValidUuid(slug) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
form := newTagsForm(companyURI(company, "/quotes/"+slug+"/tags/edit"), slug, locale)
|
form := newTagsForm(companyURI(company, "/quotes/"+slug+"/tags/edit"), slug, locale)
|
||||||
if err := form.Parse(r); err != nil {
|
if err := form.Parse(r); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
func ValidUuid(s string) bool {
|
||||||
|
if len(s) != 36 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, x := range [16]int{
|
||||||
|
0, 2, 4, 6,
|
||||||
|
9, 11,
|
||||||
|
14, 16,
|
||||||
|
19, 21,
|
||||||
|
24, 26, 28, 30, 32, 34} {
|
||||||
|
if !validHex(s[x], s[x+1]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||||
|
var xvalues = [256]byte{
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
func validHex(x1, x2 byte) bool {
|
||||||
|
return xvalues[x1] != 255 && xvalues[x2] != 255
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
in string
|
||||||
|
isUuid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var tests = []test{
|
||||||
|
{"f47ac10b-58cc-0372-8567-0e02b2c3d479", true},
|
||||||
|
{"2bc1be74-169d-4300-a239-49a1196a045d", true},
|
||||||
|
{"12bc1be74-169d-4300-a239-49a1196a045d", false},
|
||||||
|
{"2bc1be74-169d-4300-a239-49a1196a045", false},
|
||||||
|
{"2bc1be74-1x9d-4300-a239-49a1196a045d", false},
|
||||||
|
{"2bc1be74-169d-4300-a239-49a1196ag45d", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func testValidUuid(t *testing.T, in string, isUuid bool) {
|
||||||
|
if ok := ValidUuid(in); ok != isUuid {
|
||||||
|
t.Errorf("ValidUuid(%s) got %v expected %v", in, ok, isUuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUUID(t *testing.T) {
|
||||||
|
for _, tt := range tests {
|
||||||
|
testValidUuid(t, tt.in, tt.isUuid)
|
||||||
|
testValidUuid(t, strings.ToUpper(tt.in), tt.isUuid)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue