//
// Test that "max_entry_size" prefs for disk- and memory-cache prevents
// caching resources with size out of bounds
//
do_load_httpd_js();
do_get_profile();

const prefService = Cc["@mozilla.org/preferences-service;1"]
                       .getService(Ci.nsIPrefBranch);

const httpserver = new nsHttpServer();

// Repeats the given data until the total size is larger than 1K
function repeatToLargerThan1K(data) {
    while(data.length <= 1024)
        data += data;
    return data;
}

function SyncWithCacheThread(aFunc) {
  do_check_eq(sync_with_cache_IO_thread_cb.listener, null);
  sync_with_cache_IO_thread_cb.listener = aFunc;

  var cache = Cc["@mozilla.org/network/cache-service;1"].
                 getService(Ci.nsICacheService);
  var session = cache.createSession(
                  "HTTP",
                  Ci.nsICache.STORE_ANYWHERE,
                  Ci.nsICache.STREAM_BASED);

  var cacheEntry = session.asyncOpenCacheEntry(
                     "nonexistententry",
                     Ci.nsICache.ACCESS_READ,
                     sync_with_cache_IO_thread_cb);
}
var sync_with_cache_IO_thread_cb = {
  listener: null,

  onCacheEntryAvailable: function oCEA(descriptor, accessGranted, status) {
    do_check_neq(status, Cr.NS_OK);
    cb = this.listener;
    this.listener = null;
    do_execute_soon(cb);
  }
};

function clearCache() {
    var service = Components.classes["@mozilla.org/network/cache-service;1"]
        .getService(Ci.nsICacheService);
    service.evictEntries(Ci.nsICache.STORE_ANYWHERE);
}

function setupChannel(suffix, value) {
    var ios = Components.classes["@mozilla.org/network/io-service;1"]
            .getService(Ci.nsIIOService);
    var chan = ios.newChannel("http://localhost:4444" + suffix, "", null);
    var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
    httpChan.setRequestHeader("x-request", value, false);
    
    return httpChan;
}

var tests = [
             new InitializeCacheDevices(true, false), // enable and create mem-device
             new TestCacheEntrySize(
                 function() { prefService.setIntPref("browser.cache.memory.max_entry_size", 1); },
                              "012345", "9876543210", "012345"), // expect cached value
             new TestCacheEntrySize(
                 function() { prefService.setIntPref("browser.cache.memory.max_entry_size", 1); },
                              "0123456789a", "9876543210", "9876543210"), // expect fresh value
             new TestCacheEntrySize(
                 function() { prefService.setIntPref("browser.cache.memory.max_entry_size", -1); },
                              "0123456789a", "9876543210", "0123456789a"), // expect cached value

             new InitializeCacheDevices(false, true), // enable and create disk-device
             new TestCacheEntrySize(
                 function() { prefService.setIntPref("browser.cache.disk.max_entry_size", 1); },
                              "012345", "9876543210", "012345"), // expect cached value
             new TestCacheEntrySize(
                 function() { prefService.setIntPref("browser.cache.disk.max_entry_size", 1); },
                              "0123456789a", "9876543210", "9876543210"), // expect fresh value
             new TestCacheEntrySize(
                 function() { prefService.setIntPref("browser.cache.disk.max_entry_size", -1); },
                              "0123456789a", "9876543210", "0123456789a"), // expect cached value
            ];

function nextTest() {
    // We really want each test to be self-contained. Make sure cache is
    // cleared and also let all operations finish before starting a new test
    SyncWithCacheThread(function() {
        clearCache();
        SyncWithCacheThread(runNextTest);
    });
}

function runNextTest() {
    var aTest = tests.shift();
    if (!aTest) {
        httpserver.stop(do_test_finished);
        return;
    }
    do_execute_soon(function() { aTest.start(); } );
}

// Just make sure devices are created
function InitializeCacheDevices(memDevice, diskDevice) {
    this.start = function() {
        prefService.setBoolPref("browser.cache.memory.enable", memDevice);
        if (memDevice) {
            try {
                cap = prefService.getIntPref("browser.cache.memory.capacity");
            }
            catch(ex) {
                cap = 0;
            }
            if (cap == 0) {
                prefService.setIntPref("browser.cache.memory.capacity", 1024);
            }
        }
        prefService.setBoolPref("browser.cache.disk.enable", diskDevice);
        if (diskDevice) {
            try {
                cap = prefService.getIntPref("browser.cache.disk.capacity");
            }
            catch(ex) {
                cap = 0;
            }
            if (cap == 0) {
                prefService.setIntPref("browser.cache.disk.capacity", 1024);
            }
        }
        var channel = setupChannel("/bug650995", "Initial value");
        channel.asyncOpen(new ChannelListener(
            nextTest, null),
            null);
    }
}

function TestCacheEntrySize(setSizeFunc, firstRequest, secondRequest, secondExpectedReply) {

    // Initially, this test used 10 bytes as the limit for caching entries.
    // Since we now use 1K granularity we have to extend lengths to be larger
    // than 1K if it is larger than 10
    if (firstRequest.length > 10)
        firstRequest = repeatToLargerThan1K(firstRequest);
    if (secondExpectedReply.length > 10)
        secondExpectedReply = repeatToLargerThan1K(secondExpectedReply);

    this.start = function() {
        setSizeFunc();
        var channel = setupChannel("/bug650995", firstRequest);
        channel.asyncOpen(new ChannelListener(this.initialLoad, this), null);
    },

    this.initialLoad = function(request, data, ctx) {
        do_check_eq(firstRequest, data);
        var channel = setupChannel("/bug650995", secondRequest);
        do_execute_soon(function() {
            channel.asyncOpen(new ChannelListener(ctx.testAndTriggerNext, ctx), null);
            });
    },

    this.testAndTriggerNext = function(request, data, ctx) {
        do_check_eq(secondExpectedReply, data);
        do_execute_soon(nextTest);
    }
}

function run_test()
{
    httpserver.registerPathHandler("/bug650995", handler);
    httpserver.start(4444);

    prefService.setBoolPref("browser.cache.offline.enable", false);

    nextTest();
    do_test_pending();
}

function handler(metadata, response) {
    var body = "BOOM!";
    try {
        body = metadata.getHeader("x-request");
    } catch(e) {}

    response.setStatusLine(metadata.httpVersion, 200, "Ok");
    response.setHeader("Content-Type", "text/plain", false);
    response.setHeader("Cache-Control", "max-age=3600", false);
    response.bodyOutputStream.write(body, body.length);
}
