Avatar billede martin86 Nybegynder
28. oktober 2008 - 16:02 Der 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:

Formen:

Response.Write "<form method='post' action='action.asp?a=Opret&DatabaseNavn=MinDatabase&Felter=Overskrift,Tekst'>"

Response.Write "<center><table>"
Response.Write "<tr>"
    Response.Write "<td class='c_table'>Overskrift</td>"
    Response.Write "<td class='c_table'><input class='log' type='text' size='30' name='Overskrift'></td>"
Response.Write "</tr>"

Response.Write "<tr valign='top'>"
    Response.Write "<td class='c_table'>Tekst</td>"
    Response.Write "<td><textarea class='log' wrap='on' name='Tekst' rows='13' cols='60'></textarea></td>"
Response.Write "</tr>"

Response.Write "<tr>"
    Response.Write "<td class='c_table'>Status</td>"
        Response.Write "<td><select class='log' size='1' name='Status'>"
            Response.Write "<option value='0'>Skjult</option>"
            Response.Write "<option value='1'>Synlig</option>"
    Response.Write "</select></td>"
Response.Write "</tr>"

Response.Write "<tr>"
    Response.Write "<td class='c_table'>&nbsp;</td>"
    Response.Write "<td class='c_table'><input class='but' type='submit' value='Opret' name='submit'></td>"
Response.Write "</tr>"
Response.Write "</table></center>"
Response.Write "</form>"


Og funktionen:

Felter = Request.Querystring("Felter")
FeltStr = ""
ValueStr = ""

ArrFelter = Split(Felter, ",")
    For n = 0 To ubound(ArrFelter)
        FeltStr = FeltStr &","& ArrFelter
        ValueStr = ValueStr &"'"& Replace(Trim(Request.form("ArrFelter")), "'", "''")&"', "
    Next

Status = Replace(Trim(Request.form("Status")), "'", "''")

SQLgem = "Insert Into "&DatabaseNavn&" ("&FeltStr&") Values("&ValueStr&" '"&Status&"')"
Conn.Execute(SQLgem)

Set SQLgem = Nothing


Jeg håber der er en der kan hjælpe mig med at få funktionen til at fungere...

På forhånd tak.
Martin
Avatar billede keysersoze Guru
28. oktober 2008 - 16:23 #1
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.
Avatar billede martin86 Nybegynder
28. oktober 2008 - 17:10 #2
Hvis vi ligger sikkerhed og sql injection på hylden og siger at det altid vil være tekst, hvordan ville du mene at funktionen ville kunne se ud?
Avatar billede martin86 Nybegynder
29. oktober 2008 - 19:00 #3
Er der ingen der har et løsningsforslag til hvordan jeg kan få ovenstående funktion til at virke? Anyone?
Avatar billede softspot Forsker
29. oktober 2008 - 19:34 #4
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:

  <form method="post">
    <input type="hidden" name="handling" value="opdater">
    <input type="hidden" name="tabelnavn" value="person">
    <input type="hidden" name="felter" value="navn,adresse,postnr,by">
    <input type="hidden" name="noeglefelter" value="id">
   
    <input type="hidden" name="integer_4_id" value="<%=request.form("integer_4_id")%>">
   
    Navn: <input type="text" name="varchar_10_navn" value="<%=request.form("varchar_10_navn")%>"><br>
    Adresse: <input type="text" name="varchar_50_adresse" value="<%=request.form("varchar_50_adresse")%>"><br>
    Postnr: <input type="text" name="integer_4_postnr" value="<%=request.form("integer_4_postnr")%>"><br>
    By: <input type="text" name="varchar_50_by" value="<%=request.form("varchar_50_by")%>"><br>
    <input type="submit" value="gem">
  </form>

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
 
  opretHandling = strComp(request.form("handling") & "", "opret", vbTextCompare) = 0
  opdaterHandling = strComp(request.form("handling") & "", "opdater", vbTextCompare) = 0

  ' 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.
Avatar billede softspot Forsker
29. oktober 2008 - 22:35 #5
Kan du finde hoved og hale i det?
Avatar billede martin86 Nybegynder
30. oktober 2008 - 11:38 #6
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.
Avatar billede softspot Forsker
30. oktober 2008 - 11:52 #7
Kommer her - velbekomme :)
Avatar billede softspot Forsker
02. november 2008 - 22:07 #8
Tak for point :)
Avatar billede Ny bruger Nybegynder

Din løsning...

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.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester