28. oktober 2008 - 16:02Der er
7 kommentarer og 1 løsning
Insert into + split
Hej,
Jeg prøver at ligge inputs fra min form ind i en database, men i stedet for at have mange forskellige funktioner der i bund og grund gør det samme prøver jeg at lave en opret funktion der variere i opbygning alt afhængig af hvilke data der skal gemmes... Jeg håber det giver mening når i ser koden:
Idéen er for så vidt god - det er bare lidt farligt at du ikke specifikt kan gå ind og validere input hvilket åbner op for sql injection.
Du skal som minimum have en indikation på fx formnavn (og dermed tabelnavnet) om hvilken type det er, altså er det tekst, tal eller dato så du ved om værdier skal indsættes med '', ## eller ingenting omkring - og også så du ved om du skal replace ' med '' for at undgå fejl (og sql injection) ved tekst-felter.
Med mindre det er noget du kun skal bruge på din egen computer, synes jeg du sætter for meget sikkerhed over styr ved at oplyse om både tabelnavn og hvilke felter du har i databasen (eller i det mindste en delmængde heraf). Men hvis du synes det er OK, så vil jeg lade dig om det.
Mht. funktionens udformning, så er jeg da enig med keysersoze i, at du bør vide lidt mere om felternes typer og hvilke andre oplysninger du ellers kan komme i nærheden af til brug for validering. Med den løsning jeg har i tankerne vil dette dog kræve en lidt mere stringent udformning af felternes navne i formularen, da jeg vil foreslå dig at lægge felttýpen ind i feltets navn (men det er måske også det keysersoze foreslår...?). Helt konkret forestiller jeg mig noget i stil med:
Jeg vælger altså at benytte underscore som informationsseparator og at databasekolonnens navn er det sidste der findes i feltet. Desuden vælger jeg at lægge listen med felter, tabellens navn og operationen i skjulte formular-felter da det begrænser mig mindre end, hvis disse oplysninger skulle forekomme i selve URL'en. Til opdateringer har jeg behov for at vide hvilke felter der skal bruges til at identificere den række der skal opdateres, så derfor har jeg oprettet endnu en skjult felt der indeholder nøglekolonnerne. Værdien heri skal naturligvis hentes i forbindelse med indlæsning af data fra databasen.
Nu kan jeg iterere igennem mine formularfelter og udtrække de oplysninger jeg har specificeret i formularfeltet "felter" og på behørig vis oprette et command-objekt som på nogenlunde sikker vis indsætter data i den valgte tabel. Der skal stadig laves en kontrol af om tabelnavnet forsøger SQL-injection, men hvis du ellers holder dig til en simpel navngivning af dine tabeller, dvs. kun benytter bogstaver og tal, så burde et regulært udtryk rimelig let kunne spore ondsindede forsøg på at angribe din database...
Funktionen kunne så se nogenlunde således ud:
function forberedFormData(byref sql, byref arrParams) dim felter, feltkomponenter, vaerdiliste, whereliste dim tabelnavn dim feltnavn, felttype, feltlaengde dim opretHandling, opdaterHandling dim antalFelter dim rx
' udtræk tabelnavnet fra formularen tabelnavn = request.form("tabelnavn") & ""
' definer regelsættet for navngivning af tabeller i denne database ' dvs. bogstaver, tal og underscore accepteres - ikke andet! set rx = new RegExp rx.global = true rx.IgnoreCase = true rx.Pattern = "^[a-z0-9\_]+$" if not rx.test(tabelnavn) then err.Raise 10000, "forberedFormData", "Der er muligvis forsøgt SQL-injection! Kontrollér om tabelnavnet overholder standarden for navngivning." end if
if opretHandling then sql = "insert into " & tabelnavn & "(" & request.form("felter") & ") VALUES(" elseif opdaterHandling then sql = "update " & tabelnavn & " set " else err.raise 10001, "forberedFormData", "Der skal angives en opret- eller opdater-handling." end if
whereliste = "" antalFelter = 0
' foran og efterstil feltlisten med komma'er så den bliver lettere at søge i felter = "," & request.form("felter") & ","
for each fld in request.form feltkomponenter = split(fld,"_")
if ubound(feltkomponenter) = 2 then felttype = feltkomponenter(0) feltlaengde = feltkomponenter(1) feltnavn = feltkomponenter(2)
' undersøg om feltet findes i listen over felter der skal gemmes... if instr(1, felter, "," & feltnavn & ",", vbTextCompare) > 0 then if len(vaerdiliste) > 0 then vaerdiliste = vaerdiliste & "," if opretHandling then vaerdiliste = vaerdiliste & "?" else vaerdiliste = vaerdiliste & "[" & feltnavn & "] = ?" end if set param = Server.CreateObject("ADODB.Parameter") param.name = "@" & feltnavn param.value = request.Form(fld) execute "param.type = ad" & felttype param.size = feltlaengde redim preserve arrParams(ubound(arrParams) + 1) set arrParams(ubound(arrParams)) = param end if
' optæl antallet af felter der gemmes, så vi senere kan kontrollere ' om SQL-sætningen overhovedet adresserer nogle felter i tabellen. antalFelter = antalFelter + 1 end if next
' Hvis der er tale om en opdatering, skal der tages højde for at ' det er en eksisterende række der skal opdateres og at denne række ' identificeres via en eller flere felter i tabellen. if opdaterHandling then ' foran og efterstil feltlisten med komma'er så den bliver lettere at søge i felter = "," & request.form("noeglefelter") & ","
for each fld in request.form feltkomponenter = split(fld,"_")
if ubound(feltkomponenter) = 2 then felttype = feltkomponenter(0) feltlaengde = feltkomponenter(1) feltnavn = feltkomponenter(2)
' undersøg om feltet findes i listen over felter der skal gemmes... if instr(1, felter, "," & feltnavn & ",", vbTextCompare) > 0 then if len(whereliste) > 0 then whereliste = whereliste & "," whereliste = whereliste & "[" & feltnavn & "] = ?"
' opret en command-parameter som skal bruges i forbindelse med ' den endelige eksekvering af kommandoen set param = Server.CreateObject("ADODB.Parameter") param.name = "@" & feltnavn param.value = request.Form(fld) execute "param.type = ad" & felttype param.size = feltlaengde redim preserve arrParams(ubound(arrParams) + 1) set arrParams(ubound(arrParams)) = param end if
' optæl antallet af felter der gemmes, så vi senere kan kontrollere ' om SQL-sætningen overhovedet adresserer nogle felter i tabellen. antalFelter = antalFelter + 1 end if next sql = sql & vaerdiliste & " where " & whereliste else sql = sql & vaerdiliste & ")" end if
if antalFelter = 0 then err.raise 10002, "forberedFormSave", "Der er ikke angivet nogle felter." end if end function
Funktionen kan så kaldes med to output-parametre, som dels indeholder den genererede SQL dels de parametre som skal bruges i forbindelse med kaldet til databasen. Herunder er en lille stump kode som laver en testudskrift af resultaterne...
if strcomp(request.ServerVariables("request_method"), "post", vbTextCompare) = 0 then dim sql, params
sql = "" params = array()
forberedFormData sql, params
response.Write sql response.Write "<br>" response.Write "antal parametre: " & ubound(params) + 1 response.Write "<br>" for each p in params response.Write p.name & "<br>" next end if
Selve kaldet til databasen er i princippet elementært:
dim sql, params, cmd sql = "" params = array()
forberedFormData sql, params
set cmd = Server.CreateObject("ADODB.Command") set cmd.ActiveConnection = connObject ' connObject er dit åbne connectionobjekt til databasen for each p in params cmd.Parameters.Append p next cmd.Execute
Hele koden er ikke modnet (dvs. gennemtestet) og der er helt sikkert mange ting som kan optimeres og gøres bedre, men princippet er i det mindste ridset op...
Der er bla. en mangel ifht. at teste feltlisten for SQL-injection forsøg, men samme princip som for tabelnavnet kan jo benyttes.
Ja det er et rigtig godt princip.. Og Jeg er helt med på hvor du vil hen af, det har givet mig nogle gode ideer :) Lav endelig et svar så du kan få de velfortjente points.. Mange tak.
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.