1

I've been stuck on this one for a few days with no progress. Using classic ASP, I need to take an uploaded .JPG file (from html form input type='file') and base64 encode it so that I can send it to a Java web service. The Java web service is simply storing the image (in a SQL image field) in a database. I figured converting to BASE64 was the best way to transfer the parameter in the xml. Here's what I have so far:

HTML:

<label>Upload Picture</label> 
<input name="file" id="file" type="file" size=40 /> <br />

ASP:

Dim load
  Set load = new Loader
  load.initialize

Dim fileData
  fileData = load.getFileData("file")

Dim fileName
  fileName = LCase(load.getFileName("file"))

Dim fileSize
  fileSize = load.getFileSize("file")


Dim objXML
Dim objNode
Dim strB64

Set objXML = Server.CreateObject("MSXML2.DomDocument.3.0")
Set objNode = objXML.createElement("base64")

objNode.dataType = "bin.base64" 'stores binary as base64 string
objNode.nodeTypedValue = fileData 'binary value

strB64 = objNode.Text

LOADER:
the loader is a lot of code (I can copy all of it here if needed). It essentially gets all the bytes from the request and parses them into a dictionary object. Here's how it gets the data:

Class Loader
    Private dict

    Private Sub Class_Initialize
        Set dict = Server.CreateObject("Scripting.Dictionary")
    End Sub

    Public Sub Initialize
        If Request.TotalBytes > 0 Then
            Dim binData
                binData = Request.BinaryRead(Request.TotalBytes)
                getData binData
        End If
    End Sub

    Public Function getFileData(name) '"file"
        If dict.Exists(name) Then
            getFileData = dict(name).Item("Value")
            Else
            getFileData = ""
        End If
    End Function

I can post the sub that parses and stores the binary in a dictionary if needed.



The above code gives this error:

msxml3.dll error '80004005'
Error parsing '????' as bin.base64 datatype. 

on this line:

objNode.nodeTypedValue = fileData 'binary value

UPDATE:

Here's where the data is loaded to the dictionary:

Private Sub getData(rawData)
    Dim separator 
        separator = MidB(rawData, 1, InstrB(1, rawData, ChrB(13)) - 1)

    Dim lenSeparator
        lenSeparator = LenB(separator)

    Dim currentPos
        currentPos = 1
    Dim inStrByte
        inStrByte = 1
    Dim value, mValue
    Dim tempValue
        tempValue = ""

    While inStrByte > 0
        inStrByte = InStrB(currentPos, rawData, separator)
        mValue = inStrByte - currentPos

        If mValue > 1 Then
            value = MidB(rawData, currentPos, mValue)

            Dim begPos, endPos, midValue, nValue
            Dim intDict
                Set intDict = Server.CreateObject("Scripting.Dictionary")

                begPos = 1 + InStrB(1, value, ChrB(34))
                endPos = InStrB(begPos + 1, value, ChrB(34))
                nValue = endPos

            Dim nameN
                nameN = MidB(value, begPos, endPos - begPos)

            Dim nameValue, isValid
                isValid = True

                If InStrB(1, value, stringToByte("Content-Type")) > 1 Then

                    begPos = 1 + InStrB(endPos + 1, value, ChrB(34))
                    endPos = InStrB(begPos + 1, value, ChrB(34))

                    If endPos = 0 Then
                        endPos = begPos + 1
                        isValid = False
                    End If

                    midValue = MidB(value, begPos, endPos - begPos)
                        intDict.Add "FileName", trim(byteToString(midValue))

                    begPos = 14 + InStrB(endPos + 1, value, stringToByte("Content-Type:"))
                    endPos = InStrB(begPos, value, ChrB(13))

                    midValue = MidB(value, begPos, endPos - begPos)
                        intDict.Add "ContentType", trim(byteToString(midValue))

                    begPos = endPos + 4
                    endPos = LenB(value)

                    nameValue = MidB(value, begPos, ((endPos - begPos) - 1))
                Else
                    nameValue = trim(byteToString(MidB(value, nValue + 5)))
                End If

                If isValid = True Then

                    intDict.Add "Value", nameValue
                    intDict.Add "Name", nameN

                    dict.Add byteToString(nameN), intDict
                End If
        End If

        currentPos = lenSeparator + inStrByte
    Wend
End Sub

Private Function stringToByte(toConv)
    Dim tempChar, i
     For i = 1 to Len(toConv)
        tempChar = Mid(toConv, i, 1)
        stringToByte = stringToByte & chrB(AscB(tempChar))
     Next
End Function

Private Function byteToString(toConv)
     dim i
    For i = 1 to LenB(toConv)
        byteToString = byteToString & chr(AscB(MidB(toConv,i,1))) 
    Next
End Function
Cœur
  • 37,241
  • 25
  • 195
  • 267
Aaron
  • 53
  • 1
  • 9
  • 1
    Can you show where you put the binary data _in_ to the dictionary object? – Bond Jul 14 '14 at 18:33
  • I found the answer to my issue and have posted it below. For posterity's sake (and for those who are just curious), I've also added the method that loads to the dictionary above. Thanks! – Aaron Jul 15 '14 at 11:52
  • I figured you were missing the conversion from MultiByte to `VT_UI1 | VT_ARRAY`, which is why I wanted to see how you were putting the data into your dictionary. Glad you were able to figure it out on your own. – Bond Jul 15 '14 at 12:22

2 Answers2

3

I finally found something that works! Here's the link: http://www.motobit.com/tips/detpg_binarytostring/

The issue I was having was that the data from the BinaryRead was multibyte data (which ASP doesn't play well with). MultiByte data must be converted To VT_UI1 | VT_ARRAY in order for the base64 encoding via the Dom Document Object (or nearly any other function - including loading to a stream). This can be achieved using an ADO recordset object as such:

Function MultiByteToBinary(MultiByte)
  ' 2000 Antonin Foller, http://www.motobit.com
  ' MultiByteToBinary converts multibyte string To real binary data (VT_UI1 | VT_ARRAY)
  ' Using recordset
  Dim RS, LMultiByte, Binary
  Const adLongVarBinary = 205
  Set RS = CreateObject("ADODB.Recordset")
  LMultiByte = LenB(MultiByte)
  If LMultiByte>0 Then
    RS.Fields.Append "mBinary", adLongVarBinary, LMultiByte
    RS.Open
    RS.AddNew
       RS("mBinary").AppendChunk MultiByte & ChrB(0)
    RS.Update
    Binary = RS("mBinary").GetChunk(LMultiByte)
  End If
  MultiByteToBinary = Binary
End Function

Hopefully this will help someone else as well.

Aaron
  • 53
  • 1
  • 9
1

Let ADODB.Stream do your heavy lifting, this assumes you already have saved the file to disk. These are the functions for BASE64 I use, I'm not 100% sure where I originally got them.

  private function readBytes(file)
    dim inStream
    ' ADODB stream object used
    set inStream = WScript.CreateObject("ADODB.Stream")
    ' open with no arguments makes the stream an empty container
    inStream.Open
    inStream.type= TypeBinary
    inStream.LoadFromFile(file)
    readBytes = inStream.Read()
  end function

  private function encodeBase64(bytes)
    dim DM, EL
    Set DM = CreateObject("Microsoft.XMLDOM")
    ' Create temporary node with Base64 data type
    Set EL = DM.createElement("tmp")
    EL.DataType = "bin.base64"
    ' Set bytes, get encoded String
    EL.NodeTypedValue = bytes
    encodeBase64 = EL.Text
  end function

  private function decodeBase64(base64)
    dim DM, EL
    Set DM = CreateObject("Microsoft.XMLDOM")
    ' Create temporary node with Base64 data type
    Set EL = DM.createElement("tmp")
    EL.DataType = "bin.base64"
    ' Set encoded String, get bytes
    EL.Text = base64
    decodeBase64 = EL.NodeTypedValue
  end function

  private Sub writeBytes(file, bytes)
    Dim binaryStream
    Set binaryStream = CreateObject("ADODB.Stream")
    binaryStream.Type = adTypeBinary
    'Open the stream and write binary data
    binaryStream.Open
    binaryStream.Write bytes
    'Save binary data to disk
    binaryStream.SaveToFile file, adSaveCreateOverWrite
  End Sub
silver
  • 650
  • 4
  • 15
  • I use something similar but don't bother with the saving file to disk it can be done directly from the request stream. – user692942 Jul 15 '14 at 08:43
  • Your base64 encoding/decoding is the same as what I'm using (using the DOM to do the conversion). I've tried using Stream, passing in the file from the dictionary, but got a similar error about type mismatch. I finally found an answer last night and am posting it here. Thank you for the reply! – Aaron Jul 15 '14 at 11:46
  • Yep. That's because the data you get from `BinaryRead` is a _MultiByte string_ and it needs to be converted to a `VT_UI1 | VT_ARRAY` format before it can be used with an `XMLDOM` object. – Bond Jul 15 '14 at 12:18
  • I actually do the same thing, I don't save it too disk either but just trying to answer the guys question :)I convert to BASE64 on the client. – silver Jul 16 '14 at 02:28
  • @silver - Can you tell me how you call these functions in a use case- When uploading/saving a file? Thanks – kneidels Nov 10 '19 at 19:24
  • @kneidels : Form posts to ASP page, the page saves the file to disk then uses the above functions to read / convert it. But you can encode a JPG into base64 in Javascript (client side) and then save that on the backend (outside the scope of this question). – silver Nov 11 '19 at 00:00
  • @silver - thank you. I am working off this sample code here https://stackoverflow.com/questions/43646982/classic-asp-and-signature-pad , and am getting an error on server side (see question for my comment, if possible) - according to what you say, maybe i don't need to be converting server side? Would appreciate your help, TIA! – kneidels Nov 11 '19 at 06:35