11

I would like to give the users in my website the ability to download a "lnk" file. My idea is to generate this file with to contain an address that can be used only once. Is there a way to generate this file in javascript? The flow is something like -

  1. the user presses a button
  2. the javascript generates this file and downloads it to the user's machine
  3. the user sends this file to another user to use this one-time-address from his machine

Is something like this is doable in javascript from the client side? or would i need to generate this file using java server side?

Vishwanath
  • 6,284
  • 4
  • 38
  • 57
user1322801
  • 839
  • 1
  • 12
  • 27
  • check this http://www.codeproject.com/Articles/55488/File-Download-Using-JavaScript – mck Sep 02 '14 at 11:21
  • 1
    @Cory sure it does - check out `Blob`, you can create arbitrary files in JS and download them to the computer. – Benjamin Gruenbaum Sep 02 '14 at 11:22
  • The file format is specified, but not entirely trivial: http://msdn.microsoft.com/en-us/library/dd871305.aspx. – Kijewski Sep 02 '14 at 11:24
  • Well if you'd like to use Blobs as Benjamin pointed out, here is the *.lnk file format specification from Microsoft. Good luck! http://download.microsoft.com/download/9/5/E/95EF66AF-9026-4BB0-A41D-A4F81802D92C/[MS-SHLLINK].pdf – Cᴏʀʏ Sep 02 '14 at 11:25
  • 5
    @user1322801 here is how you'd do it - not gonna be a lot of fun. First - read how the [`.lnk` file format works](http://msdn.microsoft.com/en-us/library/dd871305.aspx) Microsoft released the specification. Second use a [`Blob`](https://developer.mozilla.org/en/docs/Web/API/Blob) to write that binary data in JavaScript. Finally convert that Blob into a download for all browsers for example see [this page](http://msdn.microsoft.com/en-us/library/ie/hh779016(v=vs.85).aspx) or http://stackoverflow.com/questions/3665115/create-a-file-in-memory-for-user-to-download-not-through-server – Benjamin Gruenbaum Sep 02 '14 at 11:25
  • 1
    @TomSarduy I don't believe so since it does not specify _how_ to exactly accomplish any of those things. All it does is give OP (and possible future answers) directions on how to attempt to solve the problem - an answer should be an actual solution to the problem (not production ready, but at least at a proof of concept level). I think a proof of concept is about 80 lines of code (maybe a bit less) and would round a nice amount of reputation for whoever writes it if you feel like picking up the glove and implementing it. – Benjamin Gruenbaum Sep 02 '14 at 11:33
  • 1
    @BenjaminGruenbaum: The OP did ask "*Is it doable? Is there a way to generate files in JS?*" not "*How to create lnk files?*". Your comment does indeed answer the question - I think asking for an actual solution would be off-topic or too broad. – Bergi Sep 02 '14 at 11:38
  • Do you want to target .URL links (as in remote/Internet links) or .lnk links (as in local links)? – user13500 Sep 02 '14 at 11:39
  • @Bergi I'll add an answer if someone doesn't pick up the bigger challenge of actually doing it in the next day. – Benjamin Gruenbaum Sep 02 '14 at 12:20

3 Answers3

8

This is a faithful translation of mslink.sh.

I only tested my answer in Windows 8.1, but I would think that it works in older versions of Windows, too.

function create_lnk_blob(lnk_target) {
    function hex_to_arr(s) {
        var result = Array(s.length / 2);
        for (var i = 0; i < result.length; ++i) {
            result[i] = +('0x' + s.substr(2*i, 2));
        }
        return result;
    }

    function str_to_arr(s) {
        var result = Array(s.length);
        for (var i = 0; i < s.length; ++i) {
            var c = s.charCodeAt(i);
            if (c >= 128) {
                throw Error("Only ASCII paths are suppored :-(");
            }
            result[i] = c;
        }
        return result;
    }

    function convert_CLSID_to_DATA(s) {
        var idx = [[6,2], [4,2], [2,2], [0,2],
                   [11,2], [9,2], [16,2], [14,2],
                   [19,4], [24,12]];
        var s = idx.map(function (ii) {
            return s.substr(ii[0], ii[1]);
        });
        return hex_to_arr(s.join(''));
    }

    function gen_IDLIST(s) {
        var item_size = (0x10000 + s.length + 2).toString(16).substr(1);
        return hex_to_arr(item_size.replace(/(..)(..)/, '$2$1')).concat(s);
    }

    var HeaderSize = [0x4c, 0x00,0x00,0x00],
        LinkCLSID = convert_CLSID_to_DATA("00021401-0000-0000-c000-000000000046"),
        LinkFlags = [0x01,0x01,0x00,0x00], // HasLinkTargetIDList ForceNoLinkInfo

        FileAttributes_Directory = [0x10,0x00,0x00,0x00],
        FileAttributes_File = [0x20,0x00,0x00,0x00],

        CreationTime = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
        AccessTime = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
        WriteTime = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],

        FileSize = [0x00,0x00,0x00,0x00],
        IconIndex = [0x00,0x00,0x00,0x00],
        ShowCommand = [0x01,0x00,0x00,0x00], //SW_SHOWNORMAL
        Hotkey = [0x00,0x00], // No Hotkey
        Reserved = [0x00,0x00],
        Reserved2 = [0x00,0x00,0x00,0x00],
        Reserved3 = [0x00,0x00,0x00,0x00],
        TerminalID = [0x00,0x00],

        CLSID_Computer = convert_CLSID_to_DATA("20d04fe0-3aea-1069-a2d8-08002b30309d"),
        CLSID_Network = convert_CLSID_to_DATA("208d2c60-3aea-1069-a2d7-08002b30309d"),

        PREFIX_LOCAL_ROOT = [0x2f],
        PREFIX_FOLDER = [0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
        PREFIX_FILE = [0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
        PREFIX_NETWORK_ROOT = [0xc3,0x01,0x81],
        PREFIX_NETWORK_PRINTER = [0xc3,0x02,0xc1],

        END_OF_STRING = [0x00];

    if (/.*\\+$/.test(lnk_target)) {
        lnk_target = lnk_target.replace(/\\+$/g, '');
        var target_is_folder = true;
    }

    var prefix_root, item_data, target_root, target_leaf;
    if (lnk_target.substr(0, 2) === '\\\\') {
        prefix_root = PREFIX_NETWORK_ROOT;
        item_data = [0x1f, 0x58].concat(CLSID_Network);
        target_root = lnk_target.subtr(lnk_target.lastIndexOf('\\'));
        if (/\\\\.*\\.*/.test(lnk_target)) {
            target_leaf = lnk_target.substr(lnk_target.lastIndexOf('\\') + 1);
        }
        if (target_root === '\\') {
            target_root = lnk_target;
        }
    } else {
        prefix_root = PREFIX_LOCAL_ROOT;
        item_data = [0x1f, 0x50].concat(CLSID_Computer);
        target_root = lnk_target.replace(/\\.*$/, '\\');
        if (/.*\\.*/.test(lnk_target)) {
            target_leaf = lnk_target.replace(/^.*?\\/, '');
        }
    }

    var prefix_of_target, file_attributes;
    if (!target_is_folder) {
        prefix_of_target = PREFIX_FILE;
        file_attributes = FileAttributes_File;
    } else {
        prefix_of_target = PREFIX_FOLDER;
        file_attributes = FileAttributes_Directory;
    }

    target_root = str_to_arr(target_root);
    for (var i = 1; i <= 21; ++i) {
        target_root.push(0);
    }

    var id_list_items = gen_IDLIST(item_data);
    id_list_items = id_list_items.concat(
            gen_IDLIST(prefix_root.concat(target_root, END_OF_STRING)));
    if (target_leaf) {
        target_leaf = str_to_arr(target_leaf);
        id_list_items = id_list_items.concat(
                gen_IDLIST(prefix_of_target.concat(target_leaf, END_OF_STRING)));
    }
    var id_list = gen_IDLIST(id_list_items);

    var data = [].concat(HeaderSize,
                         LinkCLSID,
                         LinkFlags,
                         file_attributes,
                         CreationTime,
                         AccessTime,
                         WriteTime,
                         FileSize,
                         IconIndex,
                         ShowCommand,
                         Hotkey,
                         Reserved,
                         Reserved2,
                         Reserved3,
                         id_list,
                         TerminalID);
    return new Blob([new Uint8Array(data)], { type: 'application/x-ms-shortcut' });
}

var blob = create_lnk_blob('C:\\Windows\\System32\\Calc.exe');

Use it like:

var blob_to_file = create_lnk_blob('C:\\Windows\\System32\\Calc.exe');
var blob_to_folder = create_lnk_blob('C:\\Users\\Myself\\Desktop\\'); // with a trailing slash

Demo: http://jsfiddle.net/5cjgLyan/2/

Kijewski
  • 25,517
  • 12
  • 101
  • 143
  • 1
    This is the best solution i have found, but the browsers seems to hate .lnk files ( I also tried generating the lnk files server side) – EKS Aug 07 '17 at 15:09
  • @kay, this is amazing! but how to add arguments to the target? – Dee Jun 12 '20 at 08:54
  • @datdinhquoc, I'm sorry, I have no idea. I only converted the script without understanding it. – Kijewski Jun 15 '20 at 12:19
  • 2
    @Dee mslink.sh made an update in version 1.2, add some new options with arguments support. I made a convert base on the latest mslink.sh and kay's answer. Which can help you, here is my gist: https://gist.github.com/zgr0629/5c2e13e07c34e867420f282941a6adf0 – Gary Jun 22 '22 at 03:57
-1

This would be simple if your website allows php.

If your script is part of an html file, just write the the javascript as if you were writing it to send a static lnk file. Then, at the lnk address part, break apart the javascript into two parts, breaking into html. Then at that point, put in

<?php /*PHP code set a variable *? /* PHP code to generate proper string*/ PRINT /*PHP variable*/
?>
lilHar
  • 1,735
  • 3
  • 21
  • 35
-3

I think make it pure client is impossible. Even the web rtc protocol need at least one iceServer to signal other client.

And I think the easiest way to do that is use http://peerjs.com/

you could first create a clinet token of the room owner

//room owner side
peer.on('open', function(my_peer_id) {
  console.log('My peer ID is: ' + my_peer_id);
});

And send the token to any other you want (by text file, web chat ...etc)

Then other connect it use the token above

//the other one
var conn = peer.connect(other_peer_id);

After the room owner detected someone entered the room.

Disconnect from signal server, so the token will become unusable

//room owner side
peer.disconnect()

About generate and read file by client side, I recommend you read article below.

  1. http://www.html5rocks.com/en/tutorials/file/dndfiles/ read from file
  2. How to use filesaver.js save as file

I believe the compatibility of fileReader api and blob doesn't matter. Since there will never be a browser which support webrtc but not support fileReader api

Community
  • 1
  • 1
Jerry
  • 938
  • 7
  • 13