ASP Classic: Can't send Base64 PDF attachement via the Sendgrid API v3, only the filename in the PDF

923 Views Asked by At

I'm trying to use the Sendgrid v3 API to send mail, using a template, personal data and one or more attachements. Been wretling with the attachement for days now, and can't seem to get around the problem of encoding the PDF to JSON and include the entire content of the PDF. When I test, I get the mail and there's an attachment to it. But just 400 bytes and Adobe thinks it's unreadable. When opening it in a text editor, the PDF contains the file name. "So, close, but no cigarr..."

Here's my code, minus the API key:

<%
Session.LCID=1053
dagen = FormatDateTime(Now,2)
bilaga1 = "D:\\www5.volvobil.net\KC-Admin-sg\docs\bilaga13.pdf"

Function ReadFile(sfilepath)
    Const adTypeText = 2
    Const adTypeBinary = 1
    Set B64Code = CreateObject("ADODB.Stream")
    b64Code.Open
    testCode = b64Code.LoadFromFile(sfilepath)
      b64Code.Position = 0
      b64Code.Type = adTypeText
      b64Code.CharSet = "us-ascii"
        dim bd
      bd = b64Code.ReadText
      B64Code.Close
      ReadFile = Base64Encode(bd)
End Function

Function Base64Encode(sText)
  Dim oXML, oNode
  Set oXML = CreateObject("Msxml2.DOMDocument.3.0")
  Set oNode = oXML.CreateElement("base64")
  oNode.dataType = "bin.base64"
  oNode.nodeTypedValue = Stream_StringToBinary(sText)
  Base64Encode = oNode.text
  Set oNode = Nothing
  Set oXML = Nothing
End Function

Function Base64Decode(ByVal vCode)
  Dim oXML, oNode
  Set oXML = CreateObject("Msxml2.DOMDocument.3.0")
  Set oNode = oXML.CreateElement("base64")
  oNode.dataType = "bin.base64"
  oNode.text = vCode
  Base64Decode = Stream_BinaryToString(oNode.nodeTypedValue)
  Set oNode = Nothing
  Set oXML = Nothing
End Function

Private Function Stream_StringToBinary(Text)
  Const adTypeText = 2
  Const adTypeBinary = 1
  Dim BinaryStream 'As New Stream
  Set BinaryStream = CreateObject("ADODB.Stream")
  BinaryStream.Type = adTypeText
  BinaryStream.CharSet = "us-ascii"
  BinaryStream.Open
  BinaryStream.WriteText Text
  BinaryStream.Position = 0
  BinaryStream.Type = adTypeBinary
  BinaryStream.Position = 0
  Stream_StringToBinary = BinaryStream.Read
  Set BinaryStream = Nothing
End Function

Private Function Stream_BinaryToString(Binary)
  Const adTypeText = 2
  Const adTypeBinary = 1
  Dim BinaryStream 'As New Stream
  Set BinaryStream = CreateObject("ADODB.Stream")
  BinaryStream.Type = adTypeBinary
  BinaryStream.Open
  BinaryStream.Write Binary
  BinaryStream.Position = 0
  BinaryStream.Type = adTypeText
  BinaryStream.CharSet = "us-ascii"
  Stream_BinaryToString = BinaryStream.ReadText
  Set BinaryStream = Nothing
End Function

toppbilden = "<img src=https://static.volvobil.se/images/mailutskick/600_header2_island.jpg alt=head border=0 />"
mailrubrik = "Byte av bil / Replacement of Car"
rubriken = "VÄLKOMMEN ATT BYTA DIN BIL"
enrubriken = "Welcome to replace your car (English version below)"


portalen = "<A HREF=https://intranet.volvocars.net/volvo-car-group/hr-portal/Pages/Company-cars-general-information-Sweden-Swedish.aspx>My Employment</A>"
portalenen = "<a href=https://intranet.volvocars.net/volvo-car-group/hr-portal/Pages/Company-cars-general-information-Sweden.aspx>My Employment</a>"

template = "d-3be42a9ce1db4461b2f72256fbb198eb"

    filen = ReadFile(bilaga1)
    filename = "bilaga13.pdf"

Response.Write "Filenamn = "& filename &" Fil = "& filen

    data = "{""from"":{""email"":""[email protected]""},""personalizations"":[{""to"":[{""email"":"""& epost &"""}],""dynamic_template_data"":{""receipt"":true,""name"":"""& namnet &""",""hallen"":"""& hallen &""",""regno"":"""& regno &""",""oldregno"":"""& oldregno &""",""bilen"":"""& bilen &""",""dagtiden"":"""& dagtiden &""",""levgubbe"":"""& levgubbe &"""}}],""attachments"": [{""content"": """& filen &""",""filename"":"""& filename &""",""type"":""application/pdf""}],""template_id"":"""& template &"""}"
    link = "https://api.sendgrid.com/v3/mail/send"

    Dim oXMLHTTP
    Set oXMLHTTP = CreateObject("Msxml2.ServerXMLHTTP.6.0")
    if oXMLHTTP is nothing then Set oXMLHTTP = CreateObject("Microsoft.XMLHTTP")

    oXMLHTTP.Open "POST", link, False
    oXMLHTTP.setRequestHeader "Content-Type", "application/json;charset=UTF-8"
    oXMLHTTP.setRequestHeader "Authorization", "Bearer <API-key>"

oXMLHTTP.send data

If oXMLHTTP.Status = 200 Then
PostData = oXMLHTTP.responseText
Else
response.Write "Status: " & oXMLHTTP.Status & " | "
response.Write oXMLHTTP.responseText

End If

SET oXMLHTTP = NOTHING

SET FormConad = NOTHING

Set objFSO = Nothing
Set objFileOut = Nothing
Set objXML = Nothing
Set objDocElem = Nothing
Set objStream = Nothing

%>

The output is the base64 code on a web page and Status: 400 | {"errors":[{"message":"Bad Request","field":null,"help":null}]} from Sendgrid.

Nothing wrong with the attached file, I've tried a few.

I'd be very grateful for your input.

Thanks,

Hasse

2

There are 2 best solutions below

5
On

Looks like are streaming the content of the file as Text and not Binary.

To summarise;

  1. The bilaga1 variable should not contain D:\\ as that will likely cause the LoadFromFile() method to error. It should be D:\ to be a valid file path.
  2. The PDF is not a text file so the ADODB.Stream should be using adTypeBinary not adTypeText.

As a side-note:

Here is a function I wrote for a SendGrid Library I built a little while ago, should give you some idea of how to add attachments.

Sub AddMailAttachment(path, contenttype, disposition, filename, id)
    Dim json: json = ""
    Dim attachment, data

    If Not IsEmpty(path) Then
      If Left(path & "", 4) = "http" Then
        Dim xhr: Set xhr = Server.CreateObject("WinHttp.WinHttpRequest.5.1")
        With xhr
          Call .Open("GET", path, False)
          Call .Send()
          If .Status = 200 Then
            data = .ResponseBody
          End If
        End With
      Else
        Dim fso: Set fso = Server.CreateObject("Scripting.FileSystemObject")
        Dim stream: Set stream = Server.CreateObject("ADODB.Stream")
        If fso.FileExists(path) Then
          With stream
            Call .Open()
            .Type = adTypeBinary
            Call .LoadFromFile(path)
            data = .Read()
          End With
        End If
      End If
      Dim contentid: contentid = Empty
      Set attachment = New MailAttachment
      If LCase(Trim(disposition & "")) = "inline" Then contentid = id
      Call attachment.Create(data, contenttype, disposition, filename, contentid)
      Call m_attachments.Add(attachment.Id, attachment)
    End If
End Sub

The code is part of a class so some elements like m_attachments (which is a Scripting.Dictionary that stores the attachments against the class) will not be accessible from this sample. The idea is to show you how the ADODB.Stream should be implemented.

Here is the MailAttachment class.

Class MailAttachment
  Private m_id
  Private m_contentid
  Private m_content
  Private m_contenttype
  Private m_dispoition
  Private m_filename
  Private m_contentlength

  Public Property Get Id
    Id = m_Id
  End Property

  Public Property Get ContentId
    ContentId = m_contentid
  End Property

  Public Property Get Content
    Content = m_content
  End Property

  Public Property Get ContentType
    ContentType = m_contenttype
  End Property

  Public Property Get Disposition
    Disposition = m_dispoition
  End Property

  Public Property Get FileName
    FileName = m_filename
  End Property

  Public Property Get Size
    Size = m_contentlength
  End Property

  Private Sub Class_Initialize()

  End Sub

  Private Function ToBase64(rabyt)
    Dim xml: Set xml = CreateObject("MSXML2.DOMDocument.3.0")
    xml.LoadXml "<root />"
    xml.documentElement.dataType = "bin.base64"
    xml.documentElement.nodeTypedValue = rabyt

    ToBase64 = Replace(xml.documentElement.Text, vbLf, "")
  End Function

  Public Sub Create(content, contenttype, disposition, filename, contentid)
    m_id = CreateGuidPlainFormat()
    m_contentid = contentid
    m_contentlength = LenB(content)
    m_content = ToBase64(content)
    m_contenttype = contenttype
    m_dispoition = disposition
    m_filename = filename
  End Sub
End Class
1
On

This actually works (A tip from Base64 Encode a ZIP file using Classic ASP and VB Script) and a function from https://www.motobit.com/.

However, it takes forever and can manage small files only, or it'll time out.

Here's the current code:

         Function Base64Encode(inData)
      Const Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
      Dim cOut, sOut, I

      For I = 1 To Len(inData) Step 3
        Dim nGroup, pOut, sGroup

        nGroup = &H10000 * Asc(Mid(inData, I, 1)) + _
          &H100 * MyASC(Mid(inData, I + 1, 1)) + MyASC(Mid(inData, I + 2, 1))

        nGroup = Oct(nGroup)

        nGroup = String(8 - Len(nGroup), "0") & nGroup

        pOut = Mid(Base64, CLng("&o" & Mid(nGroup, 1, 2)) + 1, 1) + _
          Mid(Base64, CLng("&o" & Mid(nGroup, 3, 2)) + 1, 1) + _
          Mid(Base64, CLng("&o" & Mid(nGroup, 5, 2)) + 1, 1) + _
          Mid(Base64, CLng("&o" & Mid(nGroup, 7, 2)) + 1, 1)

        sOut = sOut + pOut

      Next
      Select Case Len(inData) Mod 3
        Case 1: 
          sOut = Left(sOut, Len(sOut) - 2) + "=="
        Case 2: 
          sOut = Left(sOut, Len(sOut) - 1) + "="
      End Select
      Base64Encode = sOut
    End Function

    Function MyASC(OneChar)
      If OneChar = "" Then MyASC = 0 Else MyASC = Asc(OneChar)
    End Function

    Function BinaryToString(Binary)
      Dim I, S
      For I = 1 To LenB(Binary)
        S = S & Chr(AscB(MidB(Binary, I, 1)))
      Next
      BinaryToString = S
    End Function

    Dim objStream, strFileText
    Set objStream = Server.CreateObject("ADODB.Stream")
    objStream.Type = 1
    objStream.Open
    objStream.LoadFromFile Server.MapPath(bilaga1)

    strFileText = Base64Encode(BinaryToString(objStream.Read))
    Response.Write strFileText

    objStream.Close
    Set objStream = Nothing