Uploading to a SharePoint Image Library using AJAX

June 29, 2009

I've been working with a serial device that captures a signature recently and one of the methods of its associated ActiveX control lets me have a VBScript array of bytes that represents the captured image. It's a reasonably simple method to convert this into a JavaScript array of bytes and I thought that once I had this it would be easy enough to call the SharePoint web services using jQuery's built-in AJAX methods. Calling the web service is a piece of cake but it won't just accept an array of byes, it needs to be a Base64 encoded string, so what follows is how to get yourself one of those from the aforementioned array of bytes and pass it to the Copy web service.

For a good description on how to achieve Base64 encoding have a look at the example on this [Wikipedia]https://en.wikipedia.org/wiki/Base64#Example) page.

Firstly, the table variable contains the allowable character in a Base64 encoded string. The OctetPad() function pads a given string parameter to 8 characters in length with zeroes. The Decimal() function accepts a string parameter in binary format and converts it to its decimal equivalent.

The Base64String() method accepts an array of bytes and deals with them in chunks of 3 at a time. It converts each byte to a bit pattern and then concatenates them into a single bit pattern having a length of 24. This bit pattern is split into 4 chunks of 6, converted to a decimal number and the character from the table variable at the index equal to the decimal number is appended to the encoded string.

If the length of the array is not a multiple of 3 then we deal with the remaining 1 or 2 bytes in a slightly different manner. If there are 2 bytes remaining construct the bit pattern with these 2 bytes (giving us 16 bits) and add 2 zeroes to the end to give us 18 bits (divisible by 6). In this case we manually set the fourth Base64 encoded chracter to "=". If there is 1 byte remaining construct the bit pattern with this single byte (giving us 8 bits) and add 4 zeroes to the end to give us 12 bits (again, divisible by 6). In this case we manually set the third and fourth Base64 encoded chracters to "=".

var table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
 
function OctetPad(str) {
    return Array(8 + 1 - str.length).join('0') + str;
}
 
function Decimal(bin) {
    var len = bin.length;
    var ans = 0;
    var plc = len - 1;
    for (i = 0; i < len; i++) {
        if (bin.substr(i, 1) == 1) {
            ans = ans + Math.pow(2, plc);
        }
        else if (bin.substr(i, 1) != 0) {
            return;
        }
        plc--;
    }
    return ans;
}
 
function Base64String(bytes) {
    var base64 = '';
    while (bytes.length > 0) {
        var byte1 = null;
        var byte2 = null;
        var byte3 = null;
        var enc1 = null;
        var enc2 = null;
        var enc3 = null;
        var enc4 = null;
        byte1 = bytes.shift().toString(2);
        if (bytes.length > 0) {
            byte2 = bytes.shift().toString(2);
            if (bytes.length > 0) {
                byte3 = bytes.shift().toString(2);
            }
            else {
                enc4 = '=';
            }
        }
        else {
            enc3 = '=';
            enc4 = '=';
        }
        var bitPattern = OctetPad(byte1) + (byte2 != null ? OctetPad(byte2) : '00') + (byte3 != null ? OctetPad(byte3) : '00');
        enc1 = table.charAt(Decimal(bitPattern.substr(0, 6)));
        enc2 = table.charAt(Decimal(bitPattern.substr(6, 6)));
        if (enc3 == null) { enc3 = table.charAt(Decimal(bitPattern.substr(12, 6))); }
        if (enc4 == null) { enc4 = table.charAt(Decimal(bitPattern.substr(18, 6))); }
        base64 = base64 + enc1 + enc2 + enc3 + enc4;
    }
    return base64;
}

Our Upload() function makes use of the [jQuery]https://jquery.com/) ajax method and looks like the following: –

function Upload(jsArray, sourceUrl, destinationUrl) {
    var jsStream = Base64String(jsArray);
    var soap12Env =
        '<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"> 
            <soap12:Body> 
                <CopyIntoItems xmlns="http://schemas.microsoft.com/sharepoint/soap/"> 
                    <SourceUrl>' + sourceUrl + '</SourceUrl> 
                    <DestinationUrls> 
                        <string>' + destinationUrl + '</string> 
                    </DestinationUrls> 
                    <Fields> 
                        <FieldInformation Type="File" /> 
                    </Fields> 
                    <Stream>' + jsStream + '</Stream> 
                </CopyIntoItems> 
            </soap12:Body> 
        </soap12:Envelope>';
    $.ajax({
        url: 'http://sharepoint/_vti_bin/copy.asmx',
        type: 'POST',
        dataType: 'xml',
        data: soap12Env,
        contentType: 'text/xml; charset="utf-8"'
    });
}

I've fixed the value of the SharePoint web service URL in this function but it's probably a better idea to have it passed in as a parameter to the function. All we're doing here is creating a SOAP envelope for the Base64 stream of characters and the method would be called as follows: –

var jsArray = new Array(71,73,70,56,57,97,2,0,2,0,241,0,0,255,255,0,255,0,0,51,204,0,0,0,255,44,0,0,0,0,2,0,2,0,0,2,3,12,52,5,0,59);
var sourceUrl = 'test.gif';
var destinationUrl = 'http://sharepoint/ImageLibrary/test.gif';
Upload(jsArray, sourceUrl, destinationUrl);

In this example I've set the sourceUrl parameter to the file name we want to give the image when uploaded, this works around the "This item is a copy of…" issue when using the Copy web service.


Profile picture

Written by Stuart Whiteford
A software developer with over 20 years' experience developing business applications primarily using Microsoft technologies including ASP.NET (Web Forms, MVC and Core), SQL Server and Azure.