<|start_filename|>src/processor.js<|end_filename|>
const fs = require('fs');
const path = require('path');
const express = require('express');
const Parser = require('./parser');

const promised = (fn, ...args) => new Promise(function executor(resolve, reject) {
  fn.call(this, ...args, (err, data) => {
    if (err) {
      return reject(err);
    }
    return resolve(data);
  });
});
module.exports = class Processor {
  static convert(inputFileName) {
    const parser = new Parser({});
    const fp = promised(fs.readFile, inputFileName);
    return fp.then(data => parser.parse(data).rewrite().spit())
      .then(spitFiles => Promise.all(spitFiles.map(({ filename, content }, index) => {
        let r = Promise.resolve();
        if (index === 0 && path.extname(filename) !== '.html') {
          r = r.then(() => promised(fs.writeFile, `./out/${filename}.html`, content));
        }
        return r.then(() => promised(fs.writeFile, `./out/${filename}`, content));
      })));
  }

  static serve(port = 8080) {
    const app = express();
    const fileCache = new Map();
    app.get('/:path', (req, res) => {
      const file = req.params.path;
      if (file.endsWith('mhtml')) { // main file
        fileCache.clear(); // empty cache
        const parser = new Parser({ });
        const fp = promised(fs.readFile, `./demos/${file}`);
        fp.then(data => parser.parse(data).rewrite().spit()).then((spitFiles) => {
          for (const result of spitFiles) {
            fileCache.set(result.filename.replace(/#.*/, ''), result); // remove hash and set in cache
          }
          res.setHeader('Content-Type', spitFiles[0].type);
          res.send(spitFiles[0].content);
          res.end();
        }).catch((err) => {
          res.status(500);
          res.send(`Error: ${err}<br />${err.stack.replace(/\n/, '<br />')}`);
          res.end();
        });
        return;
      }
      const result = fileCache.get(file);
      if (!result) {
        res.status(404);
        res.send(`MISS ${file} FROM${JSON.stringify(fileCache.keys())}`);
        res.end();
        return;
      }
      res.setHeader('Content-Type', result.type);
      res.send(result.content);
      res.end();
    });
    app.listen(port);
  }
};


<|start_filename|>src/parser.test.js<|end_filename|>
const { expect } = require('chai');

const Parser = require('./parser');

describe('the mhtml parser', () => {
  it('parses basic files', () => {
    const mhtml = build([{
      body: '<html><body>Hello World!</body></html>',
      location: 'http://testim.io/',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(1);
    expect(results[0].filename).to.equal('http!testim.io');
    expect(results[0].content.trim()).to.equal('<html><body>Hello World!</body></html>');
  });

  it('throws if no boundary', () => {
    const mhtml = '<html><body>Hello World!</body></html>';
    const p = new Parser();
    expect(() => p.parse(mhtml).rewrite().spit()).to.throw();
  });

  it('parses basic files with image links', () => {
    const mhtml = build([{
      body: '<html><body><img src="http://example.com/1.jpg"></body></html>',
      location: 'http://example.com/main.html',
    }, {
      body: 'a2FrYQ==',
      transferEncoding: 'base64',
      location: 'http://example.com/1.jpg',
      type: 'image/jpeg',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(2);
    expect(results[0].filename).to.equal('http!example.com!main.html');
    expect(results[0].content.trim()).to.equal('<html><body><img src="http!example.com!1.jpg"></body></html>');
  });


  it('parses mime types of frames', () => {
    const mhtml = build([{
      body: "<html><body><img src='http://example.com/1.jpg'></body></html>",
      location: 'http://example.com/main.html',
    }, {
      body: 'a2FrYQ==',
      transferEncoding: 'base64',
      location: 'http://example.com/1.jpg',
      type: 'image/jpeg',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(2);
    expect(results[0].type).to.equal('text/html');
    expect(results[1].type).to.equal('image/jpeg');
  });

  it('parses basic files multiple with image links', () => {
    const mhtml = build([{
      body: '<html><body><img src="http://example.com/1.jpg"><img src="http://example.com/1.jpg"></body></html>',
      location: 'http://example.com/main.html',
    }, {
      body: 'a2FrYQ==',
      transferEncoding: 'base64',
      location: 'http://example.com/1.jpg',
      type: 'image/jpeg',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(2);
    expect(results[0].filename).to.equal('http!example.com!main.html');
    expect(results[0].content.trim()).to.equal('<html><body><img src="http!example.com!1.jpg"><img src="http!example.com!1.jpg"></body></html>');
  });

  it('parses iframes files', () => {
    const mhtml = build([{
      body: "<html><body><iframe src='cid:1@blink.mhtml'></iframe></body></html>",
      location: 'http://example.com/main.html',
    }, {
      body: '<html><body>Hello World</body></html>',
      location: 'http://example.com/frame.html',
      id: '<EMAIL>.mhtml',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(2);
    expect(results[0].filename).to.equal('http!example.com!main.html');
    expect(results[0].content.trim()).to.equal("<html><body><iframe src='http!example.com!frame.html'></iframe></body></html>");
  });

  it('parses nested iframes files', () => {
    const mhtml = build([{
      body: '<html><body><iframe src="cid:1@blink.mhtml"></iframe></body></html>',
      location: 'http://example.com/main.html',
    }, {
      body: '<html><body><iframe src="cid:2@blink.mhtml"></iframe></body></html>',
      location: 'http://example.com/one.html',
      id: '1@blink.mhtml',
    },
    {
      body: '<html><body>Hello World!</body></html>',
      location: 'http://example.com/two.html',
      id: '<EMAIL>.m<EMAIL>',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(3);
    expect(results[0].filename).to.equal('http!example.com!main.html');
    expect(results[0].content.trim()).to.equal('<html><body><iframe src="http!example.com!one.html"></iframe></body></html>');
    expect(results[1].content.trim()).to.equal('<html><body><iframe src="http!example.com!two.html"></iframe></body></html>');
    expect(results[2].content.trim()).to.equal('<html><body>Hello World!</body></html>');
  });

  it('parses nested images in iframes files', () => {
    const mhtml = build([{
      body: '<html><body><iframe src="cid:1@blink.mhtml"></iframe></body></html>',
      location: 'http://example.com/main.html',
    }, {
      body: '<html><body><img src="http://example.com/1.jpg"></body></html>',
      location: 'http://example.com/one.html',
      id: '1@blink.mhtml',
    }, {
      body: 'a2FrYQ==',
      transferEncoding: 'base64',
      location: 'http://example.com/1.jpg',
      type: 'image/jpeg',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(3);
    expect(results[0].filename).to.equal('http!example.com!main.html');
    expect(results[0].content.trim()).to.equal('<html><body><iframe src="http!example.com!one.html"></iframe></body></html>');
    expect(results[1].content.trim()).to.equal('<html><body><img src="http!example.com!1.jpg"></body></html>');
  });

  it('parses base tags', () => {
    const mhtml = build([{
      body: '<html><body><base href="./foo/"><img src="./1.jpg"></body></html>',
      location: 'http://example.com/one.html',
    }, {
      body: 'a2FrYQ==',
      transferEncoding: 'base64',
      location: 'http://example.com/foo/1.jpg',
      type: 'image/jpeg',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(2);
    expect(results[0].content.trim()).to.equal('<html><body><base href=""><img src="http!example.com!foo!1.jpg"></body></html>');
  });

  it('parses css files', () => {
    const mhtml = build([{
      body: 'body{}',
      type: 'text/css',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(1);
    expect(results[0].content.trim()).to.equal('body{}');
  });

  it("parses complex'ish css files", () => {
    const mhtml = build([{
      body: 'body{}h1{}h2{}.foo.bar{}a[href][css].foo#bar{}',
      type: 'text/css',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(1);
    expect(results[0].content.trim()).to.equal('body{}h1{}h2{}.foo.bar{}a[href][css].foo#bar{}');
  });

  it('replaces CSS links', () => {
    const mhtml = build([{
      body: 'body{background-image:url(./1.jpg);}',
      type: 'text/css',
    }, {
      body: 'a2FrYQ==',
      transferEncoding: 'base64',
      location: 'http://example.com/1.jpg',
      type: 'image/jpeg',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(2);
    expect(results[0].content.trim()).to.equal("body{background-image:url('http!example.com!1.jpg');}");
  });

  it('replaces absoluite CSS links', () => {
    const mhtml = build([{
      body: 'body{background-image:url(http://example.com/1.jpg);}',
      type: 'text/css',
    }, {
      body: 'a2FrYQ==',
      transferEncoding: 'base64',
      location: 'http://example.com/1.jpg',
      type: 'image/jpeg',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(2);
    expect(results[0].content.trim()).to.equal("body{background-image:url('http!example.com!1.jpg');}");
  });

  it('replaces multiple absoluite CSS links', () => {
    const mhtml = build([{
      body: 'body{background-image:url(http://example.com/1.jpg);background:url(http://example.com/1.jpg);}',
      type: 'text/css',
    }, {
      body: 'a2FrYQ==',
      transferEncoding: 'base64',
      location: 'http://example.com/1.jpg',
      type: 'image/jpeg',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(2);
    expect(results[0].content.trim()).to.equal("body{background-image:url('http!example.com!1.jpg');background:url('http!example.com!1.jpg');}");
  });

  it('extracts mime types', () => {
    const mhtml = build([{
      body: 'body{background-image:url(http://example.com/1.jpg);background:url(http://example.com/1.jpg);}',
      type: 'text/css',
    }, {
      body: 'a2FrYQ==',
      transferEncoding: 'base64',
      location: 'http://example.com/1.jpg',
      type: 'image/jpeg',
    }]);
    const p = new Parser();
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(2);
    expect(results[0].type).to.equal('text/css');
    expect(results[1].type).to.equal('image/jpeg');
  });

  it('ignores files larger than maxFileSize mime types', () => {
    const mhtml = build([{
      body: 'body{background-image:url(http://example.com/1.jpg);}',
      type: 'text/css',
    }, {
      body: `${'a2FrYQ'.repeat(1e4)}==`,
      transferEncoding: 'base64',
      location: 'http://example.com/1.jpg',
      type: 'image/jpeg',
    }]);
    const p = new Parser({ maxFileSize: 1e3 });
    const results = p.parse(mhtml).rewrite().spit();
    expect(results.length).to.equal(1);
    // didn't rewrite because of the large file removed
    expect(results[0].content.trim()).to.equal("body{background-image:url('http://example.com/1.jpg');}");
  });
});


function build(contents) {
  function buildPart(part) {
    const headers = [
      `Content-Type: ${part.type || 'text/html'}`,
      `Content-Location: ${part.location || 'http://example.com'}`,
      `Content-Transfer-Encoding: ${part.transferEncoding || 'binary'}`,
    ];
    if (part.id) {
      headers.push(`Content-ID: <${part.id}>`);
    }
    return `${headers.join('\r\n')}\r\n\r\n${part.body}`;
  }
  return header() + boundary() + contents.map(buildPart).join(boundary()) + boundary();
}
function header() {
  return `From: <Saved by Benji>
  Snapshot-Content-Location: http://example.com/
  Subject: Example Domain
  Date: Wed, 12 Dec 2018 18:00:54 -0000
  MIME-Version: 1.0
  Content-Type: multipart/related;
    type="text/html";
    boundary="----MultipartBoundary--k9eaNRyWWhTZ1w3udCxlzhXsJDCZFc9N9slfmdZezg----"


  `;
}
function boundary() {
  return '\r\n------MultipartBoundary--k9eaNRyWWhTZ1w3udCxlzhXsJDCZFc9N9slfmdZezg----\r\n'; // note this is two `-`s more
  // technically the last boundary should have two additional --s
}


<|start_filename|>src/benchmark/benchmark.js<|end_filename|>
const { mkdirSync } = require('fs');
const { resolve } = require('path');
const co = require('bluebird').coroutine;
const Processor = require('../processor');

/**
 * Should create out dir for all the out files.
 */
try {
  mkdirSync(resolve(__dirname, '../../out'));
} catch (err) {
  if (err.code !== 'EEXIST') {
    // eslint-disable-next-line no-console
    console.error(`Unable to perform benchmark because of error ${err.message}`);
    process.exit(1);
  }
}

/* eslint-disable */
const markP30 = co(function* markP30(fn, name, file) {
  global.gc();
  const start = Date.now();
  yield Promise.all(Array(30).fill().map(() => fn(file)));
  const used = process.memoryUsage().heapUsed / 1024 / 1024;

  console.log(`Converted ${name} in p=30.`,
    `Memory(${used})`,
    ` Average time: ${(Date.now() - start) / 30}`);
});

const mark = co(function* (fn, name, file, iterations = 100) {
  global.gc();
  const start = Date.now();
  for (let i = 0; i < iterations; i++) {
    yield fn(file);
  }
  const used = process.memoryUsage().heapUsed / 1024 / 1024;

  console.log(`Converted ${name} ${iterations} times.`,
    `Memory(${used})`,
    `Average time: ${(Date.now() - start) / iterations}`);
});


const bench = co(function* (name, parser) {
  console.log(name);
  yield parser("Example.com", "./demos/example.com.mhtml", 1000)
  yield parser("github", "./demos/github.node.mhtml", 100);
  yield parser("github large", "./demos/github.node.biggest.mhtml", 10);
  yield parser("github unhandled rejection", "./demos/github.unhandledrejection.mhtml", 10);
  yield parser("mdn", "./demos/mdn.mhtml", 20);
  yield parser("wikipedia", "./demos/wikipedia.mhtml", 20);
  yield parser("hn", "./demos/hn.mhtml", 20);
  yield parser("aliexpress", "./demos/aliexpress-scrolled.mhtml", 10);
  yield parser("stackoverflow", "./demos/stackoverflow.mhtml", 10);
});

/* eslint-disable */
co(function* () {
  const newParser = mark.bind(null, Processor.convert);
  const newParserParallel10 = markP30.bind(null, Processor.convert);

  yield bench("Parser Serial", newParser);
  yield bench("Parser Parallel 30", newParserParallel10);
})();



<|start_filename|>src/index.js<|end_filename|>

// mhtml parser
module.exports.Parser = require('./parser');
// mhtml reader
module.exports.Processor = require('./processor');

if (require.main === module) {
  // server mode
  module.exports.Processor.serve(process.env.PORT || 8080);
}


<|start_filename|>.eslintrc.js<|end_filename|>
module.exports = {
    "extends": "airbnb-base",
    "globals": {
        "it": true,
        "describe": true
    },
    "env": {
        "node": 1
    },
    "rules": {
        "no-use-before-define": 0,
        "no-param-reassign": 0,
        "no-restricted-syntax": 0, // alow for-of
        // this code is literally a parser, let's allow parser idioms
        "no-continue": 0,
        "no-cond-assign": 0,
        "no-bitwise": 0,
        "no-plusplus": 0,
        "comma-dangle": 0 // node 6 compat
    }
};

<|start_filename|>src/link-replacer.test.js<|end_filename|>
const { expect } = require('chai');
const {
  css, html, svg, cssWithAst, htmlWithCheerio
} = require('./link-replacer');

describe('link replacing', () => {
  describe('css', () => {
    it('replaces CSS links', () => {
      const translated = css(
        "body{background-image:url('http://example.com/1.txt');}",
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt');}");
    });

    it('replaces relative CSS links', () => {
      const translated = css(
        "body{background-image:url('./1.txt');}",
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt');}");
    });

    it('does not rewrite about:blank', () => {
      const translated = css(
        "body{background-image:url('//about:blank');}",
        new Map(),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('//about:blank');}");
    });

    it('replaces unquoted CSS links', () => {
      const translated = css(
        'body{background-image:url(./1.txt);}',
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt');}");
    });

    it('works with protocol agnostic urls', () => {
      const translated = css(
        'body{background-image:url(//example.com/1.txt);}',
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt');}");
    });

    it('replaces unquoted absolute CSS links', () => {
      const translated = css(
        'body{background-image:url(./1.txt);}',
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt');}");
    });
    it('replaces multiple CSS links', () => {
      const translated = css(
        'body{background-image:url(./1.txt);background:url(./1.txt);font:url(./1.txt);}',
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt');"
      + "background:url('http://testim.io/1.txt');font:url('http://testim.io/1.txt');}");
    });
  });

  describe('cssWithAst', () => {
    it('replaces CSS links', () => {
      const translated = cssWithAst(
        "body{background-image:url('http://example.com/1.txt');}",
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt')}");
    });
    it('replaces relative CSS links', () => {
      const translated = cssWithAst(
        "body{background-image:url('./1.txt');}",
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt')}");
    });

    it('replaces unquoted CSS links', () => {
      const translated = cssWithAst(
        'body{background-image:url(./1.txt);}',
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt')}");
    });

    it('works with protocol agnostic urls', () => {
      const translated = cssWithAst(
        'body{background-image:url(//example.com/1.txt);}',
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt')}");
    });

    it('replaces unquoted absolute CSS links', () => {
      const translated = cssWithAst(
        'body{background-image:url(./1.txt);}',
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt')}");
    });
    it('replaces multiple CSS links', () => {
      const translated = cssWithAst(
        'body{background-image:url(./1.txt);background:url(./1.txt);font:url(./1.txt);}',
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("body{background-image:url('http://testim.io/1.txt');"
      + "background:url('http://testim.io/1.txt');font:url('http://testim.io/1.txt')}");
    });
  });

  describe('html', () => {
    const wrap = el => `<html><head></head><body>${el}</body></html>`;

    it('replaces img src tags', () => {
      const translated = html(
        wrap`<img src='http://example.com/1.txt'>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img src='http://testim.io/1.txt'>`);
    });


    it('does not replace about:blank', () => {
      const translated = html(
        wrap`<img src='//about:blank'>`,
        new Map(),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img src='//about:blank'>`);
    });

    it('replaces unescaped img src tags', () => {
      const translated = html(
        wrap`<img src=http://example.com/1.txt >`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img src=http://testim.io/1.txt >`);
    });

    it('replaces unescaped img src tags without space', () => {
      const translated = html(
        wrap`<img src=http://example.com/1.txt>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img src=http://testim.io/1.txt>`);
    });

    it('ignores uninteresting unescaped attributes ', () => {
      const translated = html(
        wrap`<img kaka data foo=bar src=http://example.com/1.txt>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img kaka data foo=bar src=http://testim.io/1.txt>`);
    });


    it('ignores hash in urls', () => {
      const translated = html(
        wrap`<img src='http://example.com/1.txt#foo'>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img src='http://testim.io/1.txt#foo'>`);
    });

    it('ignores hash only urls', () => {
      const translated = html(
        wrap`<img src='#foo'>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img src='#foo'>`);
    });

    it('replaces img src tags ignoring non-value attrs', () => {
      const translated = html(
        wrap`<img disabled src='http://example.com/1.txt'>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img disabled src='http://testim.io/1.txt'>`);
    });

    it('replaces CSS style links', () => {
      const translated = html(
        "<img blabl style='background-image:url(http://example.com/1.txt);'>",
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("<img blabl style='background-image:url(&quot;http://testim.io/1.txt&quot;);'>");
    });

    it('replaces inline CSS links links', () => {
      const translated = html(
        "<style>body{background-image:url('./1.txt');}</style>",
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal("<style>body{background-image:url('http://testim.io/1.txt');}</style>");
    });

    it('ignores comments', () => {
      const translated = html(
        wrap`<img src='http://example.com/1.txt'><!-- foo -->`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img src='http://testim.io/1.txt'><!-- foo -->`);
    });

    it('replaces relative img src tags', () => {
      const translated = html(
        wrap`<img src='./1.txt'>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img src='http://testim.io/1.txt'>`);
    });

    it('replaces img srcset tags', () => {
      const translated = html(
        wrap`<img\tblabla srcset="./1.txt">`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img\tblabla srcset="http://testim.io/1.txt">`);
    });

    it('replaces link href values', () => {
      const translated = html(
        wrap`<link rel="stylesheet" href="http://example.com/1.txt">`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<link rel="stylesheet" href="http://testim.io/1.txt">`);
    });

    it('replaces relative link href values', () => {
      const translated = html(
        wrap`<link rel="stylesheet" href="./1.txt">`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<link rel="stylesheet" href="http://testim.io/1.txt">`);
    });

    it('takes base tags into acount', () => {
      const translated = html(
        wrap`<base href="./foo/"> <img src="./1.txt">`,
        new Map([['http://example.com/foo/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<base href=""> <img src="http://testim.io/1.txt">`);
    });

    it('replaces xlink:href in svg tags', () => {
      const translated = html(
        wrap`<svg><feImage xlink:href="http://example.com/1.txt"/></svg>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<svg><feImage xlink:href="http://testim.io/1.txt"/></svg>`);
    });

    it('replaces image in svg tags', () => {
      const translated = html(
        wrap`<svg><image xlink:href="http://example.com/1.txt"/></svg>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<svg><image xlink:href="http://testim.io/1.txt"/></svg>`);
    });

    it('replaces use in svg tags', () => {
      const translated = html(
        wrap`<svg><use xlink:href="http://example.com/1.txt"/></svg>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<svg><use xlink:href="http://testim.io/1.txt"/></svg>`);
    });

    it('replaces href in svg tags', () => {
      const translated = html(
        wrap`<svg><feImage href="http://example.com/1.txt"/></svg>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<svg><feImage href="http://testim.io/1.txt"/></svg>`);
    });

    it.skip('replaces meta redirects', () => {
      const translated = html(
        wrap`<meta http-equiv="refresh" content="0;URL='http://example.com/1.txt'" />`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<meta http-equiv="refresh" content="0; url=http://testim.io/1.txt">`);
    });

    it('applet code', () => {
      const translated = html(
        wrap`<applet code="http://example.com/1.txt"></applet>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<applet code="http://testim.io/1.txt"></applet>`);
    });
  });

  describe('svg', () => {
    it('replaces svg hrefs', () => {
      const translated = svg(
        '<svg><feImage href="http://example.com/1.txt"/></svg>',
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal('<svg><feImage href="http://testim.io/1.txt"/></svg>');
    });

    it('ignores unmatched svg hrefs', () => {
      const translated = svg(
        '<svg><feImage href="http://example.com/foo"/></svg>',
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal('<svg><feImage href="http://example.com/foo"/></svg>');
    });

    it('replaces svg xlink:hrefs', () => {
      const translated = svg(
        '<svg><feImage xlink:href="http://example.com/1.txt"/></svg>',
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal('<svg><feImage href="http://testim.io/1.txt"/></svg>');
    });
  });


  describe('html with cheerio', () => {
    const wrap = el => `<html><head></head><body>${el}</body></html>`;

    it('replaces img src tags', () => {
      const translated = htmlWithCheerio(
        wrap`<img src='http://example.com/1.txt'>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img src="http://testim.io/1.txt">`);
    });

    it('replaces relative img src tags', () => {
      const translated = htmlWithCheerio(
        wrap`<img src='./1.txt'>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img src="http://testim.io/1.txt">`);
    });

    it('replaces img srcset tags', () => {
      const translated = htmlWithCheerio(
        wrap`<img srcset="./1.txt">`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<img srcset="http://testim.io/1.txt">`);
    });

    it('replaces link href values', () => {
      const translated = htmlWithCheerio(
        wrap`<link rel="stylesheet" href="http://example.com/1.txt">`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<link rel="stylesheet" href="http://testim.io/1.txt">`);
    });

    it('replaces relative link href values', () => {
      const translated = htmlWithCheerio(
        wrap`<link rel="stylesheet" href="./1.txt">`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<link rel="stylesheet" href="http://testim.io/1.txt">`);
    });

    it('takes base tags into acount', () => {
      const translated = htmlWithCheerio(
        wrap`<base href="./foo/"> <img src="./1.txt">`,
        new Map([['http://example.com/foo/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<base href="./foo/"> <img src="http://testim.io/1.txt">`);
    });

    it('replaces xlink:href in svg tags', () => {
      const translated = htmlWithCheerio(
        wrap`<svg><feImage xlink:href="http://example.com/1.txt"/></svg>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<svg><feImage href="http://testim.io/1.txt"/></svg>`);
    });

    it('replaces href in svg tags', () => {
      const translated = htmlWithCheerio(
        wrap`<svg><feImage href="http://example.com/1.txt"/></svg>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<svg><feImage href="http://testim.io/1.txt"/></svg>`);
    });

    it.skip('replaces meta redirects', () => {
      const translated = htmlWithCheerio(
        wrap`<meta http-equiv="refresh" content="0;URL='http://example.com/1.txt'" />`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<meta http-equiv="refresh" content="0; url=http://testim.io/1.txt">`);
    });

    it('applet code', () => {
      const translated = htmlWithCheerio(
        wrap`<applet code="http://example.com/1.txt"></applet>`,
        new Map([['http://example.com/1.txt', 'http://testim.io/1.txt']]),
        'http://example.com'
      );
      expect(translated).to.equal(wrap`<applet code="http://testim.io/1.txt"></applet>`);
    });
  });
});
