Compare commits

..

1 Commits

Author SHA1 Message Date
Nicolas Perriault 972924df80 Added support for getElementsByClassName to JSDOMParser. 9 years ago

@ -1,208 +0,0 @@
"use strict";
module.exports = {
"parserOptions": {
"ecmaVersion": 6
},
"rules": {
// Braces only needed for multi-line arrow function blocks
// "arrow-body-style": [2, "as-needed"],
// Require spacing around =>
// "arrow-spacing": 2,
// Always require spacing around a single line block
// "block-spacing": 1,
// No newline before open brace for a block
"brace-style": 2,
// No space before always a space after a comma
"comma-spacing": [2, {"before": false, "after": true}],
// Commas at the end of the line not the start
// "comma-style": 2,
// Don't require spaces around computed properties
// "computed-property-spacing": [2, "never"],
// Functions must always return something or nothing
"consistent-return": 2,
// Require braces around blocks that start a new line
// Note that this rule is likely to be overridden on a per-directory basis
// very frequently.
// "curly": [2, "multi-line"],
// Always require a trailing EOL
"eol-last": 2,
// Require function* name()
// "generator-star-spacing": [2, {"before": false, "after": true}],
// Two space indent
"indent": [2, 2, { "SwitchCase": 1 }],
// Space after colon not before in property declarations
"key-spacing": [2, { "beforeColon": false, "afterColon": true, "mode": "minimum" }],
// Unix linebreaks
"linebreak-style": [2, "unix"],
// Always require parenthesis for new calls
"new-parens": 2,
// Use [] instead of Array()
// "no-array-constructor": 2,
// No duplicate arguments in function declarations
"no-dupe-args": 2,
// No duplicate keys in object declarations
"no-dupe-keys": 2,
// No duplicate cases in switch statements
"no-duplicate-case": 2,
// No labels
"no-labels": 2,
// If an if block ends with a return no need for an else block
"no-else-return": 2,
// No empty statements
"no-empty": 2,
// No empty character classes in regex
"no-empty-character-class": 2,
// Disallow empty destructuring
"no-empty-pattern": 2,
// No assiging to exception variable
// "no-ex-assign": 2,
// No using !! where casting to boolean is already happening
// "no-extra-boolean-cast": 2,
// No double semicolon
"no-extra-semi": 2,
// No overwriting defined functions
"no-func-assign": 2,
// Declarations in Program or Function Body
"no-inner-declarations": 2,
// No invalid regular expresions
"no-invalid-regexp": 2,
// No odd whitespace characters
"no-irregular-whitespace": 2,
// No single if block inside an else block
"no-lonely-if": 2,
// No mixing spaces and tabs in indent
"no-mixed-spaces-and-tabs": [2, "smart-tabs"],
// No unnecessary spacing
"no-multi-spaces": [2, { exceptions: { "AssignmentExpression": true, "VariableDeclarator": true, "ArrayExpression": true, "ObjectExpression": true } }],
// No reassigning native JS objects
"no-native-reassign": 2,
// No (!foo in bar)
"no-negated-in-lhs": 2,
// Nested ternary statements are confusing
"no-nested-ternary": 2,
// Use {} instead of new Object()
// "no-new-object": 2,
// No Math() or JSON()
"no-obj-calls": 2,
// No octal literals
"no-octal": 2,
// No redeclaring variables
"no-redeclare": 2,
// No unnecessary comparisons
"no-self-compare": 2,
// No declaring variables from an outer scope
"no-shadow": 2,
// No declaring variables that hide things like arguments
"no-shadow-restricted-names": 2,
// No spaces between function name and parentheses
"no-spaced-func": 2,
// No trailing whitespace
"no-trailing-spaces": 2,
// No using undeclared variables
// "no-undef": 2,
// Error on newline where a semicolon is needed
"no-unexpected-multiline": 2,
// No unreachable statements
"no-unreachable": 2,
// No expressions where a statement is expected
// "no-unused-expressions": 2,
// No declaring variables that are never used
"no-unused-vars": [2, {"vars": "all", "args": "none"}],
// No using variables before defined
// "no-use-before-define": [2, "nofunc"],
// No using with
"no-with": 2,
// No if/while/for blocks on the same line as the if/while/for statement:
"nonblock-statement-body-position": [2, "below"],
// Always require semicolon at end of statement
"semi": [2, "always"],
// Require space after keywords
"keyword-spacing": 2,
// Always use double quotes
"quotes": [2, "double", {"avoidEscape": true}],
// Require space before blocks
"space-before-blocks": 2,
// Never use spaces before function parentheses
// "space-before-function-paren": [2, { "anonymous": "always", "named": "never" }],
// Require spaces before finally, catch, etc.
// "space-before-keywords": [2, "always"],
// No space padding in parentheses
// "space-in-parens": [2, "never"],
// Require spaces around operators
// "space-infix-ops": 2,
// Require spaces after return, throw and case
// "space-return-throw-case": 2,
// ++ and -- should not need spacing
// "space-unary-ops": [2, { "words": true, "nonwords": false }],
// No comparisons to NaN
"use-isnan": 2,
// Only check typeof against valid results
"valid-typeof": 2,
},
};

1
.gitattributes vendored

@ -1 +0,0 @@
**/*.html linguist-detectable=false

@ -1,4 +0,0 @@
/benchmarks/
/test/
.gitattributes
.travis.yml

@ -1,7 +1,4 @@
language: node_js
sudo: false
node_js:
- '11.0'
script:
- npm run lint
- npm run test
- '0.10'

@ -1,15 +0,0 @@
# Community Participation Guidelines
This repository is governed by Mozilla's code of conduct and etiquette guidelines.
For more details, please read the
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
## How to Report
For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
<!--
## Project Specific Etiquette
In some cases, there will be additional project etiquette i.e.: (https://bugzilla.mozilla.org/page.cgi?id=etiquette.html).
Please update for your project.
-->

@ -1,4 +1,3 @@
/*eslint-env es6:false*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -27,6 +26,10 @@
*/
(function (global) {
function error(m) {
dump("JSDOMParser error: " + m + "\n");
}
// XML only defines these and the numeric ones:
var entityTable = {
@ -273,12 +276,11 @@
"meta": true,
"param": true,
"source": true,
"wbr": true
};
var whitespace = [" ", "\t", "\n", "\r"];
// See https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
// See http://www.w3schools.com/dom/dom_nodetype.asp
var nodeTypes = {
ELEMENT_NODE: 1,
ATTRIBUTE_NODE: 2,
@ -308,7 +310,16 @@
}
}
getElems(this);
elems._isLiveNodeList = true;
return elems;
}
function getElementsByClassName(classname) {
var elems = [];
var re = new RegExp('(^| )' + classname + '( |$)');
var nodes = this.getElementsByTagName("*");
for (var i=0, j=nodes.length; i<j; i++)
if (re.test(nodes[i].className))
elems.push(nodes[i]);
return elems;
}
@ -454,15 +465,16 @@
else
this.children.push(newNode);
}
} else if (oldNode.nodeType === Node.ELEMENT_NODE) {
} else {
// new node is not an element node.
// if the old one was, update its element siblings:
if (oldNode.previousElementSibling)
oldNode.previousElementSibling.nextElementSibling = oldNode.nextElementSibling;
if (oldNode.nextElementSibling)
oldNode.nextElementSibling.previousElementSibling = oldNode.previousElementSibling;
this.children.splice(this.children.indexOf(oldNode), 1);
if (oldNode.nodeType === Node.ELEMENT_NODE) {
if (oldNode.previousElementSibling)
oldNode.previousElementSibling.nextElementSibling = oldNode.nextElementSibling;
if (oldNode.nextElementSibling)
oldNode.nextElementSibling.previousElementSibling = oldNode.previousElementSibling;
this.children.splice(this.children.indexOf(oldNode), 1);
}
// If the old node wasn't an element, neither the new nor the old node was an element,
// and the children array and its members shouldn't need any updating.
}
@ -482,8 +494,8 @@
__JSDOMParser__: true,
};
for (var nodeType in nodeTypes) {
Node[nodeType] = Node.prototype[nodeType] = nodeTypes[nodeType];
for (var i in nodeTypes) {
Node[i] = Node.prototype[i] = nodeTypes[i];
}
var Attribute = function (name, value) {
@ -497,9 +509,17 @@
},
setValue: function(newValue) {
this._value = newValue;
delete this._decodedValue;
},
getEncodedValue: function() {
return encodeHTML(this._value);
setDecodedValue: function(newValue) {
this._value = encodeHTML(newValue);
this._decodedValue = newValue;
},
getDecodedValue: function() {
if (typeof this._decodedValue === "undefined") {
this._decodedValue = (this._value && decodeHTML(this._value)) || "";
}
return this._decodedValue;
},
};
@ -544,10 +564,9 @@
this._textContent = newText;
delete this._innerHTML;
},
};
}
var Document = function (url) {
this.documentURI = url;
var Document = function () {
this.styleSheets = [];
this.childNodes = [];
this.children = [];
@ -562,6 +581,8 @@
getElementsByTagName: getElementsByTagName,
getElementsByClassName: getElementsByClassName,
getElementById: function (id) {
function getElem(node) {
var length = node.children.length;
@ -587,30 +608,9 @@
node.textContent = text;
return node;
},
get baseURI() {
if (!this.hasOwnProperty("_baseURI")) {
this._baseURI = this.documentURI;
var baseElements = this.getElementsByTagName("base");
var href = baseElements[0] && baseElements[0].getAttribute("href");
if (href) {
try {
this._baseURI = (new URL(href, this._baseURI)).href;
} catch (ex) {/* Just fall back to documentURI */}
}
}
return this._baseURI;
},
};
var Element = function (tag) {
// We use this to find the closing tag.
this._matchingTag = tag;
// We're explicitly a non-namespace aware parser, we just pretend it's all HTML.
var lastColonIndex = tag.lastIndexOf(":");
if (lastColonIndex != -1) {
tag = tag.substring(lastColonIndex + 1);
}
this.attributes = [];
this.childNodes = [];
this.children = [];
@ -627,6 +627,8 @@
getElementsByTagName: getElementsByTagName,
getElementsByClassName: getElementsByClassName,
get className() {
return this.getAttribute("class") || "";
},
@ -659,14 +661,6 @@
this.setAttribute("src", str);
},
get srcset() {
return this.getAttribute("srcset") || "";
},
set srcset(str) {
this.setAttribute("srcset", str);
},
get nodeName() {
return this.tagName;
},
@ -683,14 +677,14 @@
for (var j = 0; j < child.attributes.length; j++) {
var attr = child.attributes[j];
// the attribute value will be HTML escaped.
var val = attr.getEncodedValue();
var val = attr.value;
var quote = (val.indexOf('"') === -1 ? '"' : "'");
arr.push(" " + attr.name + "=" + quote + val + quote);
arr.push(" " + attr.name + '=' + quote + val + quote);
}
if (child.localName in voidElems && !child.childNodes.length) {
if (child.localName in voidElems) {
// if this is a self-closing element, end it here
arr.push("/>");
arr.push(">");
} else {
// otherwise, add its children
arr.push(">");
@ -705,6 +699,7 @@
}
// Using Array.join() avoids the overhead from lazy string concatenation.
// See http://blog.cdleary.com/2012/01/string-representation-in-spidermonkey/#ropes
var arr = [];
getHTML(this);
return arr.join("");
@ -713,13 +708,12 @@
set innerHTML(html) {
var parser = new JSDOMParser();
var node = parser.parse(html);
var i;
for (i = this.childNodes.length; --i >= 0;) {
for (var i = this.childNodes.length; --i >= 0;) {
this.childNodes[i].parentNode = null;
}
this.childNodes = node.childNodes;
this.children = node.children;
for (i = this.childNodes.length; --i >= 0;) {
for (var i = this.childNodes.length; --i >= 0;) {
this.childNodes[i].parentNode = this;
}
},
@ -760,9 +754,8 @@
getAttribute: function (name) {
for (var i = this.attributes.length; --i >= 0;) {
var attr = this.attributes[i];
if (attr.name === name) {
return attr.value;
}
if (attr.name === name)
return attr.getDecodedValue();
}
return undefined;
},
@ -771,11 +764,11 @@
for (var i = this.attributes.length; --i >= 0;) {
var attr = this.attributes[i];
if (attr.name === name) {
attr.setValue(value);
attr.setDecodedValue(value);
return;
}
}
this.attributes.push(new Attribute(name, value));
this.attributes.push(new Attribute(name, encodeHTML(value)));
},
removeAttribute: function (name) {
@ -786,13 +779,7 @@
break;
}
}
},
hasAttribute: function (name) {
return this.attributes.some(function (attr) {
return attr.name == name;
});
},
}
};
var Style = function (node) {
@ -850,7 +837,7 @@
Style.prototype.__defineSetter__(jsName, function (value) {
this.setStyle(cssName, value);
});
})(styleMap[jsName]);
}) (styleMap[jsName]);
}
var JSDOMParser = function () {
@ -868,20 +855,9 @@
// makeElementNode(), which saves us from having to allocate a new array
// every time.
this.retPair = [];
this.errorState = "";
};
JSDOMParser.prototype = {
error: function(m) {
if (typeof dump !== "undefined") {
dump("JSDOMParser error: " + m + "\n");
} else if (typeof console !== "undefined") {
console.log("JSDOMParser error: " + m + "\n");
}
this.errorState += m + "\n";
},
/**
* Look at the next character without advancing the index.
*/
@ -936,14 +912,14 @@
// After a '=', we should see a '"' for the attribute value
var c = this.nextChar();
if (c !== '"' && c !== "'") {
this.error("Error reading attribute " + name + ", expecting '\"'");
error("Error reading attribute " + name + ", expecting '\"'");
return;
}
// Read the attribute value (and consume the matching quote)
var value = this.readString(c);
node.attributes.push(new Attribute(name, decodeHTML(value)));
node.attributes.push(new Attribute(name, value));
return;
},
@ -968,7 +944,7 @@
strBuf.push(c);
c = this.nextChar();
}
var tag = strBuf.join("");
var tag = strBuf.join('');
if (!tag)
return false;
@ -979,9 +955,7 @@
while (c !== "/" && c !== ">") {
if (c === undefined)
return false;
while (whitespace.indexOf(this.html[this.currentChar++]) != -1) {
// Advance cursor to first non-whitespace char.
}
while (whitespace.indexOf(this.html[this.currentChar++]) != -1);
this.currentChar--;
c = this.nextChar();
if (c !== "/" && c !== ">") {
@ -991,19 +965,19 @@
}
// If this is a self-closing tag, read '/>'
var closed = false;
var closed = tag in voidElems;
if (c === "/") {
closed = true;
c = this.nextChar();
if (c !== ">") {
this.error("expected '>' to close " + tag);
error("expected '>' to close " + tag);
return false;
}
}
retPair[0] = node;
retPair[1] = closed;
return true;
return true
},
/**
@ -1045,6 +1019,46 @@
}
},
readScript: function (node) {
while (this.currentChar < this.html.length) {
var c = this.nextChar();
var nextC = this.peekNext();
if (c === "<") {
if (nextC === "!" || nextC === "?") {
// We're still before the ! or ? that is starting this comment:
this.currentChar++;
node.appendChild(this.discardNextComment());
continue;
}
if (nextC === "/" && this.html.substr(this.currentChar, 8 /*"/script>".length */).toLowerCase() == "/script>") {
// Go back before the '<' so we find the end tag.
this.currentChar--;
// Done with this script tag, the caller will close:
return;
}
}
// Either c wasn't a '<' or it was but we couldn't find either a comment
// or a closing script tag, so we should just parse as text until the next one
// comes along:
var haveTextNode = node.lastChild && node.lastChild.nodeType === Node.TEXT_NODE;
var textNode = haveTextNode ? node.lastChild : new Text();
var n = this.html.indexOf("<", this.currentChar);
// Decrement this to include the current character *afterwards* so we don't get stuck
// looking for the same < all the time.
this.currentChar--;
if (n === -1) {
textNode.innerHTML += this.html.substring(this.currentChar, this.html.length);
this.currentChar = this.html.length;
} else {
textNode.innerHTML += this.html.substring(this.currentChar, n);
this.currentChar = n;
}
if (!haveTextNode)
node.appendChild(textNode);
}
},
discardNextComment: function() {
if (this.match("--")) {
this.discardTo("-->");
@ -1075,31 +1089,18 @@
return null;
// Read any text as Text node
var textNode;
if (c !== "<") {
--this.currentChar;
textNode = new Text();
var node = new Text();
var n = this.html.indexOf("<", this.currentChar);
if (n === -1) {
textNode.innerHTML = this.html.substring(this.currentChar, this.html.length);
node.innerHTML = this.html.substring(this.currentChar, this.html.length);
this.currentChar = this.html.length;
} else {
textNode.innerHTML = this.html.substring(this.currentChar, n);
node.innerHTML = this.html.substring(this.currentChar, n);
this.currentChar = n;
}
return textNode;
}
if (this.match("![CDATA[")) {
var endChar = this.html.indexOf("]]>", this.currentChar);
if (endChar === -1) {
this.error("unclosed CDATA section");
return null;
}
textNode = new Text();
textNode.textContent = this.html.substring(this.currentChar, endChar);
this.currentChar = endChar + ("]]>").length;
return textNode;
return node;
}
c = this.peekNext();
@ -1132,10 +1133,14 @@
// If this isn't a void Element, read its child nodes
if (!closed) {
this.readChildren(node);
var closingTag = "</" + node._matchingTag + ">";
if (localName == "script") {
this.readScript(node);
} else {
this.readChildren(node);
}
var closingTag = "</" + localName + ">";
if (!this.match(closingTag)) {
this.error("expected '" + closingTag + "' and got " + this.html.substr(this.currentChar, closingTag.length));
error("expected '" + closingTag + "'");
return null;
}
}
@ -1159,9 +1164,9 @@
/**
* Parses an HTML string and returns a JS implementation of the Document.
*/
parse: function (html, url) {
parse: function (html) {
this.html = html;
var doc = this.doc = new Document(url);
var doc = this.doc = new Document();
this.readChildren(doc);
// If this is an HTML document, remove root-level children except for the
@ -1189,8 +1194,4 @@
// Attach JSDOMParser to the global scope
global.JSDOMParser = JSDOMParser;
})(this);
if (typeof module === "object") {
module.exports = this.JSDOMParser;
}
}) (this);

@ -1,101 +1,43 @@
# Readability.js
A standalone version of the readability library used for Firefox Reader View.
[![Build Status](https://travis-ci.org/mozilla/readability.svg?branch=master)](https://travis-ci.org/mozilla/readability)
A standalone version of the readability library used for Firefox Reader View. Any changes to Readability.js itself should be reviewed by an appropriate Firefox/toolkit peer, such as [@leibovic](https://github.com/leibovic) or [@thebnich](https://github.com/thebnich), since these changes will be automatically merged to mozilla-central.
## Contributing
## Usage on the web
For outstanding issues, see the issue list in this repo, as well as this bug list: https://bugzilla.mozilla.org/show_bug.cgi?id=1102450
To parse a document, you must create a new `Readability` object from a DOM document object, and then call `parse()`. Here's an example:
To test local changes to Readability.js, you can run your own instance of [readable-proxy](https://github.com/n1k0/readable-proxy/) to compare an original test page to its reader-ized content.
## Usage
To parse a document, you must create a new `Readability` object from a URI object and a document, and then call `parse()`. Here's an example:
```javascript
var article = new Readability(document).parse();
var location = document.location;
var uri = {
spec: location.href,
host: location.host,
prePath: location.protocol + "//" + location.host,
scheme: location.protocol.substr(0, location.protocol.indexOf(":")),
pathBase: location.protocol + "//" + location.host + location.pathname.substr(0, location.pathname.lastIndexOf("/") + 1)
};
var article = new Readability(uri, document).parse();
```
This `article` object will contain the following properties:
* `uri`: original `uri` object that was passed to constructor
* `title`: article title
* `content`: HTML string of processed article content
* `length`: length of an article, in characters
* `excerpt`: article description, or short excerpt from the content
* `length`: length of article, in characters
* `excerpt`: article description, or short excerpt from content
* `byline`: author metadata
* `dir`: content direction
If you're using Readability on the web, you will likely be able to use a `document` reference
from elsewhere (e.g. fetched via XMLHttpRequest, in a same-origin `<iframe>` you have access to, etc.).
### Optional
Readability's `parse()` works by modifying the DOM. This removes some elements in the web page.
You could avoid this by passing the clone of the `document` object while creating a `Readability` object.
```
var documentClone = document.cloneNode(true);
var article = new Readability(documentClone).parse();
```
## Usage from Node.js
Readability is available on npm:
```bash
npm install @mozilla/readability
```
In Node.js, you won't generally have a DOM document object. To obtain one, you can use external
libraries like [jsdom](https://github.com/jsdom/jsdom). While this repository contains a parser of
its own (`JSDOMParser`), that is restricted to reading XML-compatible markup and therefore we do
not recommend it for general use.
If you're using `jsdom` to create a DOM object, you should ensure that the page doesn't run (page)
scripts (avoid fetching remote resources etc.) as well as passing it the page's URI as the `url`
property of the `options` object you pass the `JSDOM` constructor.
### Example:
```js
var { Readability } = require('@mozilla/readability');
var JSDOM = require('jsdom').JSDOM;
var doc = new JSDOM("<body>Here's a bunch of text</body>", {
url: "https://www.example.com/the-page-i-got-the-source-from"
});
let reader = new Readability(doc.window.document);
let article = reader.parse();
```
## What's Readability-readerable?
It's a quick-and-dirty way of figuring out if it's plausible that the contents of a given
document are suitable for processing with Readability. It is likely to produce both false
positives and false negatives. The reason it exists is to avoid bogging down a time-sensitive
process (like loading and showing the user a webpage) with the complex logic in the core of
Readability. Improvements to its logic (while not deteriorating its performance) are very
welcome.
## Security
If you're going to use Readability with untrusted input (whether in HTML or DOM form), we
**strongly** recommend you use a sanitizer library like
[DOMPurify](https://github.com/cure53/DOMPurify) to avoid script injection when you use
the output of Readability. We would also recommend using
[CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to add further defense-in-depth
restrictions to what you allow the resulting content to do. The Firefox integration of
reader mode uses both of these techniques itself. Sanitizing unsafe content out of the input
is explicitly not something we aim to do as part of Readability itself - there are other
good sanitizer libraries out there, use them!
## Contributing
[![Build Status](https://travis-ci.org/mozilla/readability.svg?branch=master)](https://travis-ci.org/mozilla/readability)
For outstanding issues, see the issue list in this repo, as well as this [bug list](https://bugzilla.mozilla.org/buglist.cgi?component=Reader%20Mode&product=Toolkit&bug_status=__open__&limit=0).
Any changes to Readability.js itself should be reviewed by an [appropriate Firefox/toolkit peer](https://wiki.mozilla.org/Modules/Firefox), such as [@gijsk](https://github.com/gijsk), since these changes will be merged to mozilla-central and shipped in Firefox.
To test local changes to Readability.js, you can use the [automated tests](#tests). There's a [node script](https://github.com/mozilla/readability/blob/master/test/generate-testcase.js) to help you create new ones.
## Tests
Please run [eslint](http://eslint.org/) as a first check that your changes are valid JS and adhere to our style guidelines.
To run the test suite:
$ mocha test/test-*.js

@ -1,99 +0,0 @@
/* eslint-env es6:false */
/*
* Copyright (c) 2010 Arc90 Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This code is heavily based on Arc90's readability.js (1.7.1) script
* available at: http://code.google.com/p/arc90labs-readability
*/
var REGEXPS = {
// NOTE: These two regular expressions are duplicated in
// Readability.js. Please keep both copies in sync.
unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
okMaybeItsACandidate: /and|article|body|column|content|main|shadow/i,
};
function isNodeVisible(node) {
// Have to null-check node.style and node.className.indexOf to deal with SVG and MathML nodes.
return (!node.style || node.style.display != "none")
&& !node.hasAttribute("hidden")
//check for "fallback-image" so that wikimedia math images are displayed
&& (!node.hasAttribute("aria-hidden") || node.getAttribute("aria-hidden") != "true" || (node.className && node.className.indexOf && node.className.indexOf("fallback-image") !== -1));
}
/**
* Decides whether or not the document is reader-able without parsing the whole thing.
*
* @return boolean Whether or not we suspect Readability.parse() will suceeed at returning an article object.
*/
function isProbablyReaderable(doc, isVisible) {
if (!isVisible) {
isVisible = isNodeVisible;
}
var nodes = doc.querySelectorAll("p, pre");
// Get <div> nodes which have <br> node(s) and append them into the `nodes` variable.
// Some articles' DOM structures might look like
// <div>
// Sentences<br>
// <br>
// Sentences<br>
// </div>
var brNodes = doc.querySelectorAll("div > br");
if (brNodes.length) {
var set = new Set(nodes);
[].forEach.call(brNodes, function(node) {
set.add(node.parentNode);
});
nodes = Array.from(set);
}
var score = 0;
// This is a little cheeky, we use the accumulator 'score' to decide what to return from
// this callback:
return [].some.call(nodes, function(node) {
if (!isVisible(node))
return false;
var matchString = node.className + " " + node.id;
if (REGEXPS.unlikelyCandidates.test(matchString) &&
!REGEXPS.okMaybeItsACandidate.test(matchString)) {
return false;
}
if (node.matches("li p")) {
return false;
}
var textContentLength = node.textContent.trim().length;
if (textContentLength < 140) {
return false;
}
score += Math.sqrt(textContentLength - 140);
if (score > 20) {
return true;
}
return false;
});
}
if (typeof module === "object") {
module.exports = isProbablyReaderable;
}

File diff suppressed because it is too large Load Diff

@ -1,12 +1,10 @@
var clean = require("matcha/lib/matcha/reporters/clean");
var clean = require('matcha/lib/matcha/reporters/clean');
function average(list) {
if (!list.length)
return 0;
var sum = list.reduce(function(previous, current) {
return previous + current;
});
var sum = list.reduce(function(previous, current) { return previous + current; });
return (sum / list.length).toFixed(0);
}
@ -18,20 +16,20 @@ module.exports = function(runner, utils) {
var color = utils.color;
var results = {};
var currentResults = [];
runner.on("bench end", function(benchResults) {
currentResults.push(benchResults.ops);
runner.on('bench end', function(results) {
currentResults.push(results.ops);
});
runner.on("suite end", function(suite) {
runner.on('suite end', function(suite) {
var avg = humanize(average(currentResults));
console.log(padBefore(avg + " op/s", 22) + " » " + suite.title);
console.log(padBefore(avg + ' op/s', 22) + ' » ' + suite.title);
console.log();
results[suite.title] = avg;
currentResults = [];
});
runner.on("end", function() {
runner.on('end', function() {
for (var k in results) {
console.log(color(padBefore(k, 30) + ": ", "gray") + results[k] + " op/s");
console.log(color(padBefore(k, 30) + ': ', 'gray') + results[k] + ' op/s');
}
console.log();
});

@ -1,8 +1,8 @@
var getTestPages = require("../test/utils").getTestPages;
var getTestPages = require("../test/bootstrap").getTestPages;
var { Readability, isProbablyReaderable } = require("../index");
var JSDOM = require("jsdom").JSDOM;
var JSDOMParser = require("../JSDOMParser");
var readability = require("../index.js");
var Readability = readability.Readability;
var JSDOMParser = readability.JSDOMParser;
var referenceTestPages = [
"002",
@ -42,25 +42,17 @@ suite("Readability test page perf", function () {
set("iterations", 1);
set("type", "static");
var uri = {
spec: "http://fakehost/test/page.html",
host: "fakehost",
prePath: "http://fakehost",
scheme: "http",
pathBase: "http://fakehost/test"
};
testPages.forEach(function(testPage) {
var doc = new JSDOMParser().parse(testPage.source);
bench(testPage.dir + " readability perf", function() {
new Readability(doc).parse();
});
});
});
suite("isProbablyReaderable perf", function () {
set("iterations", 1);
set("type", "static");
testPages.forEach(function(testPage) {
var uri = "http://fakehost/test/page.html";
var doc = new JSDOM(testPage.source, {
url: uri,
}).window.document;
bench(testPage.dir + " readability perf", function() {
isProbablyReaderable(doc);
new Readability(uri, doc).parse();
});
});
});

@ -1,7 +1,27 @@
var Readability = require("./Readability");
var isProbablyReaderable = require("./Readability-readerable");
var path = require("path");
var fs = require("fs");
// We want to load Readability and JSDOMParser, which aren't set up as commonjs libraries,
// and so we need to do some hocus-pocus with 'vm' to import them on a separate scope
// (identical) scope context.
var vm = require("vm");
var readabilityPath = path.join(__dirname, "Readability.js");
var jsdomPath = path.join(__dirname, "JSDOMParser.js");
var scopeContext = {};
// We generally expect dump() and console.{whatever} to work, so make these available
// in the scope we're using:
scopeContext.dump = console.log
scopeContext.console = console;
// Actually load files. NB: if either of the files has parse errors,
// node is dumb and shows you a syntax error *at this callsite* . Don't try to find
// a syntax error on this line, there isn't one. Go look in the file it's loading instead.
vm.runInNewContext(fs.readFileSync(jsdomPath), scopeContext, jsdomPath);
vm.runInNewContext(fs.readFileSync(readabilityPath), scopeContext, readabilityPath);
module.exports = {
Readability: Readability,
isProbablyReaderable: isProbablyReaderable
Readability: scopeContext.Readability,
JSDOMParser: scopeContext.JSDOMParser
};

@ -1,10 +1,9 @@
{
"name": "@mozilla/readability",
"version": "0.3.0",
"name": "readability",
"version": "0.0.1",
"description": "A standalone version of the readability library used for Firefox Reader View.",
"main": "index.js",
"main": "Readability.js",
"scripts": {
"lint": "eslint .",
"test": "mocha test/test-*.js",
"generate-testcase": "node test/generate-testcase.js",
"perf": "matcha benchmarks/benchmarks.js",
@ -15,22 +14,16 @@
"url": "https://github.com/mozilla/readability"
},
"author": "",
"license": "Apache-2.0",
"license": "Apache2",
"bugs": {
"url": "https://github.com/mozilla/readability/issues"
},
"engines": {
"node": ">=10.0.0"
},
"homepage": "https://github.com/mozilla/readability",
"devDependencies": {
"chai": "^2.1.*",
"eslint": ">=4.2",
"htmltidy2": "^0.3.0",
"js-beautify": "^1.13.0",
"jsdom": "^13.1",
"html": "0.0.*",
"jsdom": "^3.1.2",
"matcha": "^0.6.0",
"mocha": "^2.2.*",
"sinon": "^7.3.2"
"mocha": "^2.2.*"
}
}

24
test/bootstrap.js vendored

@ -0,0 +1,24 @@
var path = require("path");
var fs = require("fs");
var prettyPrint = require("html").prettyPrint;
function readFile(path) {
return fs.readFileSync(path, {encoding: "utf-8"}).trim();
}
function readJSON(path) {
return JSON.parse(readFile(path));
}
var testPageRoot = path.join(__dirname, "test-pages");
exports.getTestPages = function() {
return fs.readdirSync(testPageRoot).map(function(dir) {
return {
dir: dir,
source: readFile(path.join(testPageRoot, dir, "source.html")),
expectedContent: readFile(path.join(testPageRoot, dir, "expected.html")),
expectedMetadata: readJSON(path.join(testPageRoot, dir, "expected-metadata.json")),
};
});
};

@ -2,50 +2,47 @@ var debug = false;
var path = require("path");
var fs = require("fs");
var JSDOM = require("jsdom").JSDOM;
var prettyPrint = require("./utils").prettyPrint;
var jsdom = require("jsdom").jsdom;
var prettyPrint = require("html").prettyPrint;
var serializeDocument = require("jsdom").serializeDocument;
var http = require("http");
var urlparse = require("url").parse;
var htmltidy = require("htmltidy2").tidy;
var { Readability, isProbablyReaderable } = require("../index");
var JSDOMParser = require("../JSDOMParser");
var readability = require("../index");
var Readability = readability.Readability;
var JSDOMParser = readability.JSDOMParser;
var FFX_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0";
var testcaseRoot = path.join(__dirname, "test-pages");
var argURL = process.argv[3]; // Could be undefined, we'll warn if it is if that is an issue.
function generateTestcase(slug) {
var destRoot = path.join(testcaseRoot, slug);
if (process.argv.length < 3) {
console.error("Need at least a destination slug and potentially a URL (if the slug doesn't have source).");
process.exit(0);
return;
}
fs.mkdir(destRoot, function(err) {
if (err) {
var sourceFile = path.join(destRoot, "source.html");
fs.exists(sourceFile, function(exists) {
if (exists) {
fs.readFile(sourceFile, {encoding: "utf-8"}, function(readFileErr, data) {
if (readFileErr) {
console.error("Source existed but couldn't be read?");
process.exit(1);
return;
}
onResponseReceived(null, data, destRoot);
});
} else {
fetchSource(argURL, function(fetchErr, data) {
onResponseReceived(fetchErr, data, destRoot);
});
}
});
return;
}
fetchSource(argURL, function(fetchErr, data) {
onResponseReceived(fetchErr, data, destRoot);
var slug = process.argv[2];
var url = process.argv[3]; // Could be undefined, we'll warn if it is if that is an issue.
var destRoot = path.join(__dirname, "test-pages", slug);
fs.mkdir(destRoot, function(err) {
if (err) {
var sourceFile = path.join(destRoot, "source.html");
fs.exists(sourceFile, function(exists) {
if (exists) {
fs.readFile(sourceFile, {encoding: "utf-8"}, function(err, data) {
if (err) {
console.error("Source existed but couldn't be read?");
process.exit(1);
return;
}
onResponseReceived(data);
});
} else {
fetchSource(url, onResponseReceived);
}
});
});
}
return;
}
fetchSource(url, onResponseReceived);
});
function fetchSource(url, callbackFn) {
if (!url) {
@ -57,10 +54,7 @@ function fetchSource(url, callbackFn) {
if (url.indexOf("https") == 0) {
client = require("https");
}
var options = urlparse(url);
options.headers = {"User-Agent": FFX_UA};
client.get(options, function(response) {
client.get(url, function(response) {
if (debug) {
console.log("STATUS:", response.statusCode);
console.log("HEADERS:", JSON.stringify(response.headers));
@ -74,27 +68,14 @@ function fetchSource(url, callbackFn) {
if (debug) {
console.log("End received");
}
sanitizeSource(rv, callbackFn);
// Sanitize:
rv = prettyPrint(serializeDocument(jsdom(rv)));
callbackFn(rv);
});
});
}
function sanitizeSource(html, callbackFn) {
htmltidy(new JSDOM(html).serialize(), {
"indent": true,
"indent-spaces": 4,
"numeric-entities": true,
"output-xhtml": true,
"wrap": 0
}, callbackFn);
}
function onResponseReceived(error, source, destRoot) {
if (error) {
console.error("Couldn't tidy source html!");
console.error(error);
return;
}
function onResponseReceived(source) {
if (debug) {
console.log("writing");
}
@ -113,25 +94,19 @@ function onResponseReceived(error, source, destRoot) {
}
function runReadability(source, destPath, metadataDestPath) {
var uri = "http://fakehost/test/page.html";
var doc = new JSDOMParser().parse(source, uri);
var myReader, result, readerable;
var doc = new JSDOMParser().parse(source);
var uri = {
spec: "http://fakehost/test/page.html",
host: "fakehost",
prePath: "http://fakehost",
scheme: "http",
pathBase: "http://fakehost/test/"
};
var readability, result, readerable;
try {
// We pass `caption` as a class to check that passing in extra classes works,
// given that it appears in some of the test documents.
myReader = new Readability(doc, { classesToPreserve: ["caption"] });
result = myReader.parse();
} catch (ex) {
console.error(ex);
ex.stack.forEach(console.log.bind(console));
}
// Use jsdom for isProbablyReaderable because it supports querySelectorAll
try {
var jsdomDoc = new JSDOM(source, {
url: uri,
}).window.document;
myReader = new Readability(jsdomDoc);
readerable = isProbablyReaderable(jsdomDoc);
readability = new Readability(uri, doc);
readerable = readability.isProbablyReaderable();
result = readability.parse();
} catch (ex) {
console.error(ex);
ex.stack.forEach(console.log.bind(console));
@ -141,46 +116,28 @@ function runReadability(source, destPath, metadataDestPath) {
return;
}
fs.writeFile(destPath, prettyPrint(result.content), function(fileWriteErr) {
if (fileWriteErr) {
fs.writeFile(destPath, prettyPrint(result.content), function(err) {
if (err) {
console.error("Couldn't write data to expected.html!");
console.error(fileWriteErr);
console.error(err);
}
// Delete the result data we don't care about checking.
delete result.uri;
delete result.content;
delete result.textContent;
delete result.length;
// Add isProbablyReaderable result
result.readerable = readerable;
fs.writeFile(metadataDestPath, JSON.stringify(result, null, 2) + "\n", function(metadataWriteErr) {
if (metadataWriteErr) {
fs.writeFile(metadataDestPath, JSON.stringify(result, null, 2) + "\n", function(err) {
if (err) {
console.error("Couldn't write data to expected-metadata.json!");
console.error(metadataWriteErr);
console.error(err);
}
});
});
}
if (process.argv.length < 3) {
console.error("Need at least a destination slug and potentially a URL (if the slug doesn't have source).");
process.exit(0);
throw "Abort";
}
if (process.argv[2] === "all") {
fs.readdir(testcaseRoot, function(err, files) {
if (err) {
console.error("error reading testcaseses");
return;
}
files.forEach(function(file) {
generateTestcase(file);
process.exit(0);
});
});
} else {
generateTestcase(process.argv[2]);
}

@ -1,23 +0,0 @@
var JSDOM = require("jsdom").JSDOM;
var chai = require("chai");
chai.config.includeStack = true;
var expect = chai.expect;
var testPages = require("./utils").getTestPages();
var isProbablyReaderable = require("../index").isProbablyReaderable;
describe("isProbablyReaderable - test pages", function() {
testPages.forEach(function(testPage) {
var uri = "http://fakehost/test/page.html";
describe(testPage.dir, function() {
var doc = new JSDOM(testPage.source, {
url: uri,
}).window.document;
var expected = testPage.expectedMetadata.readerable;
it("The result should " + (expected ? "" : "not ") + "be readerable", function() {
expect(isProbablyReaderable(doc)).eql(expected);
});
});
});
});

@ -1,14 +1,18 @@
var path = require("path");
var fs = require("fs");
var chai = require("chai");
chai.config.includeStack = true;
var expect = chai.expect;
var JSDOMParser = require("../JSDOMParser");
var readability = require("../index.js");
var JSDOMParser = readability.JSDOMParser;
var BASETESTCASE = '<html><body><p>Some text and <a class="someclass" href="#">a link</a></p>' +
'<div id="foo">With a <script>With &lt; fancy " characters in it because' +
'<div id="foo">With a <script>With < fancy " characters in it because' +
'</script> that is fun.<span>And another node to make it harder</span></div><form><input type="text"/><input type="number"/>Here\'s a form</form></body></html>';
var baseDoc = new JSDOMParser().parse(BASETESTCASE, "http://fakehost/");
var baseDoc = new JSDOMParser().parse(BASETESTCASE);
describe("Test JSDOM functionality", function() {
function nodeExpect(actual, expected) {
@ -31,16 +35,11 @@ describe("Test JSDOM functionality", function() {
expect(generatedHTML).eql('Some text and <a class="someclass" href="#">a link</a>');
var scriptNode = baseDoc.getElementsByTagName("script")[0];
generatedHTML = scriptNode.innerHTML;
expect(generatedHTML).eql('With &lt; fancy " characters in it because');
expect(generatedHTML).eql('With < fancy " characters in it because');
expect(scriptNode.textContent).eql('With < fancy " characters in it because');
});
it("should have basic URI information", function() {
expect(baseDoc.documentURI, "http://fakehost/");
expect(baseDoc.baseURI, "http://fakehost/");
});
it("should deal with script tags", function() {
// Check our script parsing worked:
var scripts = baseDoc.getElementsByTagName("script");
@ -114,7 +113,7 @@ describe("Test JSDOM functionality", function() {
});
it("should have a working replaceChild", function() {
var parent = baseDoc.getElementsByTagName("div")[0];
var parent = baseDoc.getElementsByTagName('div')[0];
var p = baseDoc.createElement("p");
p.setAttribute("id", "my-replaced-kid");
var childCount = parent.childNodes.length;
@ -227,8 +226,8 @@ describe("Test HTML escaping", function() {
});
it("should handle decimal and hex escape sequences", function() {
var parsedDoc = new JSDOMParser().parse("<p>&#32;&#x20;</p>");
expect(parsedDoc.getElementsByTagName("p")[0].textContent).eql(" ");
var doc = new JSDOMParser().parse("<p>&#32;&#x20;</p>");
expect(doc.getElementsByTagName("p")[0].textContent).eql(" ");
});
});
@ -240,7 +239,7 @@ describe("Script parsing", function() {
expect(doc.firstChild.tagName).eql("SCRIPT");
expect(doc.firstChild.textContent).eql("");
expect(doc.firstChild.children.length).eql(0);
expect(doc.firstChild.childNodes.length).eql(0);
expect(doc.firstChild.childNodes.length).eql(1);
});
it("should strip !-based comments within script tags", function() {
@ -249,11 +248,11 @@ describe("Script parsing", function() {
expect(doc.firstChild.tagName).eql("SCRIPT");
expect(doc.firstChild.textContent).eql("");
expect(doc.firstChild.children.length).eql(0);
expect(doc.firstChild.childNodes.length).eql(0);
expect(doc.firstChild.childNodes.length).eql(1);
});
it("should strip any other nodes within script tags", function() {
var html = "<script>&lt;div>Hello, I'm not really in a &lt;/div></script>";
var html = "<script><div>Hello, I'm not really in a </div></script>";
var doc = new JSDOMParser().parse(html);
expect(doc.firstChild.tagName).eql("SCRIPT");
expect(doc.firstChild.textContent).eql("<div>Hello, I'm not really in a </div>");
@ -261,17 +260,8 @@ describe("Script parsing", function() {
expect(doc.firstChild.childNodes.length).eql(1);
});
it("should strip any other invalid script nodes within script tags", function() {
var html = '<script>&lt;script src="foo.js">&lt;/script></script>';
var doc = new JSDOMParser().parse(html);
expect(doc.firstChild.tagName).eql("SCRIPT");
expect(doc.firstChild.textContent).eql("<script src=\"foo.js\"></script>");
expect(doc.firstChild.children.length).eql(0);
expect(doc.firstChild.childNodes.length).eql(1);
});
it("should not be confused by partial closing tags", function() {
var html = "<script>var x = '&lt;script>Hi&lt;' + '/script>';</script>";
var html = "<script>var x = '<script>Hi<' + '/script>';</script>";
var doc = new JSDOMParser().parse(html);
expect(doc.firstChild.tagName).eql("SCRIPT");
expect(doc.firstChild.textContent).eql("var x = '<script>Hi<' + '/script>';");
@ -293,45 +283,35 @@ describe("Tag local name case handling", function() {
});
});
describe("Recovery from self-closing tags that have close tags", function() {
it("should handle delayed closing of a tag", function() {
var html = "<div><input><p>I'm in an input</p></input></div>";
var doc = new JSDOMParser().parse(html);
expect(doc.firstChild.localName).eql("div");
expect(doc.firstChild.childNodes.length).eql(1);
expect(doc.firstChild.firstChild.localName).eql("input");
expect(doc.firstChild.firstChild.childNodes.length).eql(1);
expect(doc.firstChild.firstChild.firstChild.localName).eql("p");
describe("#getElementsByClassName", function() {
var doc;
beforeEach(function() {
var html = '<div>' +
' <div class="foo bar">plop</div>' +
' <div class="bar">plap</div>' +
' <p>baz</p>' +
'</div>';
doc = new JSDOMParser().parse(html);
});
});
describe("baseURI parsing", function() {
it("should handle various types of relative and absolute base URIs", function() {
function checkBase(base, expectedResult) {
var html = "<html><head><base href='" + base + "'></base></head><body/></html>";
var doc = new JSDOMParser().parse(html, "http://fakehost/some/dir/");
expect(doc.baseURI).eql(expectedResult);
}
it("should find elements having provided class name", function() {
expect(doc.getElementsByClassName("foo")).to.have.length.of(1);
});
checkBase("relative/path", "http://fakehost/some/dir/relative/path");
checkBase("/path", "http://fakehost/path");
checkBase("http://absolute/", "http://absolute/");
checkBase("//absolute/path", "http://absolute/path");
it("shouldn't find unmacthing elements", function() {
expect(doc.getElementsByClassName("baz")).to.have.length.of(0);
});
});
describe("namespace workarounds", function() {
it("should handle random namespace information in the serialized DOM", function() {
var html = "<a0:html><a0:body><a0:DIV><a0:svG><a0:clippath/></a0:svG></a0:DIV></a0:body></a0:html>";
var doc = new JSDOMParser().parse(html);
var div = doc.getElementsByTagName("div")[0];
expect(div.tagName).eql("DIV");
expect(div.localName).eql("div");
expect(div.firstChild.tagName).eql("SVG");
expect(div.firstChild.localName).eql("svg");
expect(div.firstChild.firstChild.tagName).eql("CLIPPATH");
expect(div.firstChild.firstChild.localName).eql("clippath");
expect(doc.documentElement).eql(doc.firstChild);
expect(doc.body).eql(doc.documentElement.firstChild);
it("should retrieve multiple elements matching a single class name", function() {
expect(doc.getElementsByClassName("bar")).to.have.length.of(2);
});
it("should search combined class names", function() {
expect(doc.getElementsByClassName("foo bar")).to.have.length.of(1);
});
it("should perform a case-sensitive search", function() {
expect(doc.getElementsByClassName("FOO")).to.have.length.of(0);
});
});

@ -1,8 +1,6 @@
{
"title": "Get your Frontend JavaScript Code Covered | Code",
"byline": "Nicolas Perriault —",
"dir": null,
"excerpt": "Nicolas Perriault's homepage.",
"siteName": null,
"readerable": true
}

@ -1,23 +1,48 @@
<div id="readability-page-1" class="page">
<section>
<p><strong>So finally you're <a href="http://fakehost/code/2013/testing-frontend-javascript-code-using-mocha-chai-and-sinon/">testing your frontend JavaScript code</a>? Great! The more you write tests, the more confident you are with your code… but how much precisely? That's where <a href="http://en.wikipedia.org/wiki/Code_coverage">code coverage</a> might help.</strong>
<p><strong>So finally you're <a href="http://fakehost/code/2013/testing-frontend-javascript-code-using-mocha-chai-and-sinon/">testing your frontend JavaScript code</a>? Great! The more you
write tests, the more confident you are with your code… but how much precisely?
That's where <a href="http://en.wikipedia.org/wiki/Code_coverage">code coverage</a> might
help.</strong>
</p>
<p>The idea behind code coverage is to record which parts of your code (functions, statements, conditionals and so on) have been executed by your test suite, to compute metrics out of these data and usually to provide tools for navigating and inspecting them.</p>
<p>Not a lot of frontend developers I know actually test their frontend code, and I can barely imagine how many of them have ever setup code coverage… Mostly because there are not many frontend-oriented tools in this area I guess.</p>
<p>Actually I've only found one which provides an adapter for <a href="http://visionmedia.github.io/mocha/">Mocha</a> and actually works…</p>
<blockquote>
<p>Drinking game for web devs: <br />(1) Think of a noun <br />(2) Google "&lt;noun&gt;.js" <br />(3) If a library with that name exists - drink</p>— Shay Friedman (@ironshay) <a href="https://twitter.com/ironshay/statuses/370525864523743232">August 22, 2013</a>
<p>The idea behind code coverage is to record which parts of your code (functions,
statements, conditionals and so on) have been executed by your test suite,
to compute metrics out of these data and usually to provide tools for navigating
and inspecting them.</p>
<p>Not a lot of frontend developers I know actually test their frontend code,
and I can barely imagine how many of them have ever setup code coverage…
Mostly because there are not many frontend-oriented tools in this area
I guess.</p>
<p>Actually I've only found one which provides an adapter for <a href="http://visionmedia.github.io/mocha/">Mocha</a> and
actually works…</p>
<blockquote class="twitter-tweet tw-align-center">
<p>Drinking game for web devs:
<br>(1) Think of a noun
<br>(2) Google "&lt;noun&gt;.js"
<br>(3) If a library with that name exists - drink</p>— Shay Friedman (@ironshay)
<a
href="https://twitter.com/ironshay/statuses/370525864523743232">August 22, 2013</a>
</blockquote>
<p><strong><a href="http://blanketjs.org/">Blanket.js</a></strong> is an <em>easy to install, easy to configure, and easy to use JavaScript code coverage library that works both in-browser and with nodejs.</em>
<p><strong><a href="http://blanketjs.org/">Blanket.js</a></strong> is an <em>easy to install, easy to configure,
and easy to use JavaScript code coverage library that works both in-browser and
with nodejs.</em>
</p>
<p>Its use is dead easy, adding Blanket support to your Mocha test suite is just matter of adding this simple line to your HTML test file:</p>
<pre><code>&lt;script src="vendor/blanket.js"
<p>Its use is dead easy, adding Blanket support to your Mocha test suite
is just matter of adding this simple line to your HTML test file:</p>
<pre><code>&lt;script src="vendor/blanket.js"
data-cover-adapter="vendor/mocha-blanket.js"&gt;&lt;/script&gt;
</code></pre>
<p>Source files: <a href="https://raw.github.com/alex-seville/blanket/master/dist/qunit/blanket.min.js">blanket.js</a>, <a href="https://raw.github.com/alex-seville/blanket/master/src/adapters/mocha-blanket.js">mocha-blanket.js</a>
<p>Source files: <a href="https://raw.github.com/alex-seville/blanket/master/dist/qunit/blanket.min.js">blanket.js</a>,
<a
href="https://raw.github.com/alex-seville/blanket/master/src/adapters/mocha-blanket.js">mocha-blanket.js</a>
</p>
<p>As an example, let's reuse the silly <code>Cow</code> example we used <a href="http://fakehost/code/2013/testing-frontend-javascript-code-using-mocha-chai-and-sinon/">in a previous episode</a>:</p>
<pre><code>// cow.js
<p>As an example, let's reuse the silly <code>Cow</code> example we used
<a
href="http://fakehost/code/2013/testing-frontend-javascript-code-using-mocha-chai-and-sinon/">in a previous episode</a>:</p>
<pre><code>// cow.js
(function(exports) {
"use strict";
@ -35,8 +60,9 @@
};
})(this);
</code></pre>
<p>And its test suite, powered by Mocha and <a href="http://chaijs.com/">Chai</a>:</p>
<pre><code>var expect = chai.expect;
<pre><code>var expect = chai.expect;
describe("Cow", function() {
describe("constructor", function() {
@ -59,8 +85,10 @@ describe("Cow", function() {
});
});
</code></pre>
<p>Let's create the HTML test file for it, featuring Blanket and its adapter for Mocha:</p>
<pre><code>&lt;!DOCTYPE html&gt;
<p>Let's create the HTML test file for it, featuring Blanket and its adapter
for Mocha:</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset="utf-8"&gt;
@ -82,18 +110,31 @@ describe("Cow", function() {
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><strong>Notes</strong>:</p>
<ul>
<li>Notice the <code>data-cover</code> attribute we added to the script tag loading the source of our library;</li>
<li>The HTML test file <em>must</em> be served over HTTP for the adapter to be loaded.</li>
<li>Notice the <code>data-cover</code> attribute we added to the script tag
loading the source of our library;</li>
<li>The HTML test file <em>must</em> be served over HTTP for the adapter to
be loaded.</li>
</ul>
<p>Running the tests now gives us something like this:</p>
<p>
<img alt="screenshot" src="http://fakehost/static/code/2013/blanket-coverage.png" />
<img alt="screenshot" src="http://fakehost/static/code/2013/blanket-coverage.png">
</p>
<p>As you can see, the report at the bottom highlights that we haven't actually tested the case where an error is raised in case a target name is missing. We've been informed of that, nothing more, nothing less. We simply know we're missing a test here. Isn't this cool? I think so!</p>
<p>Just remember that code coverage will only <a href="http://codebetter.com/karlseguin/2008/12/09/code-coverage-use-it-wisely/">bring you numbers</a> and raw information, not actual proofs that the whole of your <em>code logic</em> has been actually covered. If you ask me, the best inputs you can get about your code logic and implementation ever are the ones issued out of <a href="http://www.extremeprogramming.org/rules/pair.html">pair programming</a> sessions and <a href="http://alexgaynor.net/2013/sep/26/effective-code-review/">code reviews</a> — but that's another story.</p>
<p>As you can see, the report at the bottom highlights that we haven't actually
tested the case where an error is raised in case a target name is missing.
We've been informed of that, nothing more, nothing less. We simply know
we're missing a test here. Isn't this cool? I think so!</p>
<p>Just remember that code coverage will only <a href="http://codebetter.com/karlseguin/2008/12/09/code-coverage-use-it-wisely/">bring you numbers</a> and
raw information, not actual proofs that the whole of your <em>code logic</em> has
been actually covered. If you ask me, the best inputs you can get about
your code logic and implementation ever are the ones issued out of <a href="http://www.extremeprogramming.org/rules/pair.html">pair programming</a>
sessions
and <a href="http://alexgaynor.net/2013/sep/26/effective-code-review/">code reviews</a>
but that's another story.</p>
<p><strong>So is code coverage silver bullet? No. Is it useful? Definitely. Happy testing!</strong>
</p>
</section>
</div>
</div>

@ -2,22 +2,22 @@
<html class="no-js" lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Get your Frontend JavaScript Code Covered | Code | Nicolas Perriault</title>
<meta
name="description" content="Nicolas Perriault's homepage."/>
<meta name="viewport" content="width=device-width"/>
name="description" content="Nicolas Perriault's homepage.">
<meta name="viewport" content="width=device-width">
<link href="//fonts.googleapis.com/css?family=Asap:400,400italic,700,700italic&amp;subset=latin,latin-ext"
rel="stylesheet" type="text/css"/>
<link rel="stylesheet" type="text/css" href="/static/packed.css?1412806084"/>
<link rel="alternate" type="application/rss+xml" href="/code/feed/" title="Code (RSS)"/>
rel="stylesheet" type="text/css">
<link rel="stylesheet" type="text/css" href="/static/packed.css?1412806084">
<link rel="alternate" type="application/rss+xml" href="/code/feed/" title="Code (RSS)">
<link rel="alternate" type="application/rss+xml" href="/photography/feed/"
title="Photography (RSS)"/>
<link rel="alternate" type="application/rss+xml" href="/talks/feed/" title="Talks (RSS)"/>
title="Photography (RSS)">
<link rel="alternate" type="application/rss+xml" href="/talks/feed/" title="Talks (RSS)">
<link rel="alternate" type="application/rss+xml" href="/carnet/feed/"
title="Carnet (RSS)"/>
<link rel="alternate" type="application/rss+xml" href="/feed/" title="Everything (RSS)"/>
title="Carnet (RSS)">
<link rel="alternate" type="application/rss+xml" href="/feed/" title="Everything (RSS)">
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
@ -35,7 +35,7 @@
</header>
<main class="contents" role="main">
<article lang="en" class="code" itemscope="" itemtype="http://schema.org/BlogPosting">
<link itemprop="url" href="/code/2013/get-your-frontend-javascript-code-covered/"/>
<link itemprop="url" href="/code/2013/get-your-frontend-javascript-code-covered/">
<header>
<h2><a itemprop="name" href="/code/2013/get-your-frontend-javascript-code-covered/">Get your Frontend JavaScript Code Covered</a></h2>
</header>
@ -57,9 +57,9 @@ help.</strong>
actually works…</p>
<blockquote class="twitter-tweet tw-align-center">
<p>Drinking game for web devs:
<br />(1) Think of a noun
<br />(2) Google "&lt;noun&gt;.js"
<br />(3) If a library with that name exists - drink</p>— Shay Friedman (@ironshay)
<br>(1) Think of a noun
<br>(2) Google "&lt;noun&gt;.js"
<br>(3) If a library with that name exists - drink</p>— Shay Friedman (@ironshay)
<a
href="https://twitter.com/ironshay/statuses/370525864523743232">August 22, 2013</a>
</blockquote>
@ -158,7 +158,7 @@ describe("Cow", function() {
</ul>
<p>Running the tests now gives us something like this:</p>
<p>
<img alt="screenshot" src="/static/code/2013/blanket-coverage.png"/>
<img alt="screenshot" src="/static/code/2013/blanket-coverage.png">
</p>
<p>As you can see, the report at the bottom highlights that we haven't actually
tested the case where an error is raised in case a target name is missing.
@ -186,7 +186,7 @@ sessions
</p>
</aside>
<hr/>
<hr>
<nav> <a class="prev" href="/code/2013/functional-javascript-for-crawling-the-web/">Functional JavaScript for crawling the Web</a>
|
<a
@ -223,11 +223,11 @@ sessions
<!-- /container -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>
window.jQuery || document.write('&lt;script src="js/libs/jquery-1.7.1.min.js">&lt;\/script>')
window.jQuery || document.write('<script src="js/libs/jquery-1.7.1.min.js"><\/script>')
</script>
<script type="text/javascript" src="/static/js/libs/prettify/prettify.js"></script>
<script type="text/javascript" src="/static/js/app.js"></script>
<script src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
</body>
</html>
</html>

@ -1,8 +1,6 @@
{
"title": "This API is so Fetching!",
"byline": "Nikhil Marathe",
"dir": null,
"excerpt": "For more than a decade the Web has used XMLHttpRequest (XHR) to achieve asynchronous requests in JavaScript. While very useful, XHR is not a very ...",
"siteName": "Mozilla Hacks the Web developer blog",
"readerable": true
}

@ -1,20 +1,45 @@
<div id="readability-page-1" class="page">
<div id="content-main">
<article role="article">
<p>For more than a decade the Web has used XMLHttpRequest (XHR) to achieve asynchronous requests in JavaScript. While very useful, XHR is not a very nice API. It suffers from lack of separation of concerns. The input, output and state are all managed by interacting with one object, and state is tracked using events. Also, the event-based model doesnt play well with JavaScripts recent focus on Promise- and generator-based asynchronous programming.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a> intends to fix most of these problems. It does this by introducing the same primitives to JS that are used in the HTTP protocol. In addition, it introduces a utility function <code>fetch()</code> that succinctly captures the intention of retrieving a resource from the network.</p>
<p>The <a href="https://fetch.spec.whatwg.org/">Fetch specification</a>, which defines the API, nails down the semantics of a user agent fetching a resource. This, combined with ServiceWorkers, is an attempt to:</p>
<ol>
<li>Improve the offline experience.</li>
<li>Expose the building blocks of the Web to the platform as part of the <a href="https://extensiblewebmanifesto.org/">extensible web movement</a>.</li>
</ol>
<p>As of this writing, the Fetch API is available in Firefox 39 (currently Nightly) and Chrome 42 (currently dev). Github has a <a href="https://github.com/github/fetch">Fetch polyfill</a>.</p>
<h2>Feature detection</h2>
<p>Fetch API support can be detected by checking for <code>Headers</code>,<code>Request</code>, <code>Response</code> or <code>fetch</code> on the <code>window</code> or <code>worker</code> scope.</p>
<h2>Simple fetching</h2>
<p>The most useful, high-level part of the Fetch API is the <code>fetch()</code> function. In its simplest form it takes a URL and returns a promise that resolves to the response. The response is captured as a <code>Response</code> object.</p>
<div>
<pre>fetch<span>(</span><span>"/data.json"</span><span>)</span>.<span>then</span><span>(</span><span>function</span><span>(</span>res<span>)</span> <span>{</span>
<article role="article">
<p>For more than a decade the Web has used XMLHttpRequest (XHR) to achieve
asynchronous requests in JavaScript. While very useful, XHR is not a very
nice API. It suffers from lack of separation of concerns. The input, output
and state are all managed by interacting with one object, and state is
tracked using events. Also, the event-based model doesnt play well with
JavaScripts recent focus on Promise- and generator-based asynchronous
programming.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a> intends
to fix most of these problems. It does this by introducing the same primitives
to JS that are used in the HTTP protocol. In addition, it introduces a
utility function <code>fetch()</code> that succinctly captures the intention
of retrieving a resource from the network.</p>
<p>The <a href="https://fetch.spec.whatwg.org">Fetch specification</a>, which
defines the API, nails down the semantics of a user agent fetching a resource.
This, combined with ServiceWorkers, is an attempt to:</p>
<ol>
<li>Improve the offline experience.</li>
<li>Expose the building blocks of the Web to the platform as part of the
<a
href="https://extensiblewebmanifesto.org/">extensible web movement</a>.</li>
</ol>
<p>As of this writing, the Fetch API is available in Firefox 39 (currently
Nightly) and Chrome 42 (currently dev). Github has a <a href="https://github.com/github/fetch">Fetch polyfill</a>.</p>
<h2>Feature detection</h2>
<p>Fetch API support can be detected by checking for <code>Headers</code>,<code>Request</code>, <code>Response</code> or <code>fetch</code> on
the <code>window</code> or <code>worker</code> scope.</p>
<h2>Simple fetching</h2>
<p>The most useful, high-level part of the Fetch API is the <code>fetch()</code> function.
In its simplest form it takes a URL and returns a promise that resolves
to the response. The response is captured as a <code>Response</code> object.</p>
<div
class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript">fetch<span>(</span><span>"/data.json"</span><span>)</span>.<span>then</span><span>(</span><span>function</span><span>(</span>res<span>)</span> <span>{</span>
<span>// res instanceof Response == true.</span>
<span>if</span> <span>(</span>res.<span>ok</span><span>)</span> <span>{</span>
res.<span>json</span><span>(</span><span>)</span>.<span>then</span><span>(</span><span>function</span><span>(</span>data<span>)</span> <span>{</span>
@ -26,10 +51,18 @@
<span>}</span><span>,</span> <span>function</span><span>(</span>e<span>)</span> <span>{</span>
console.<span>log</span><span>(</span><span>"Fetch failed!"</span><span>,</span> e<span>)</span><span>;</span>
<span>}</span><span>)</span><span>;</span></pre>
</div>
<p>Submitting some parameters, it would look like this:</p>
<div>
<pre>fetch<span>(</span><span>"http://www.example.org/submit.php"</span><span>,</span> <span>{</span>
</td>
</tr>
</tbody>
</table>
</div>
<p>Submitting some parameters, it would look like this:</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript">fetch<span>(</span><span>"http://www.example.org/submit.php"</span><span>,</span> <span>{</span>
method<span>:</span> <span>"POST"</span><span>,</span>
headers<span>:</span> <span>{</span>
<span>"Content-Type"</span><span>:</span> <span>"application/x-www-form-urlencoded"</span>
@ -44,33 +77,68 @@
<span>}</span><span>,</span> <span>function</span><span>(</span>e<span>)</span> <span>{</span>
alert<span>(</span><span>"Error submitting form!"</span><span>)</span><span>;</span>
<span>}</span><span>)</span><span>;</span></pre>
</div>
<p>The <code>fetch()</code> functions arguments are the same as those passed to the <br />
<code>Request()</code> constructor, so you may directly pass arbitrarily complex requests to <code>fetch()</code> as discussed below.
</p>
<h2>Headers</h2>
<p>Fetch introduces 3 interfaces. These are <code>Headers</code>, <code>Request</code> and <br />
<code>Response</code>. They map directly to the underlying HTTP concepts, but have <br />certain visibility filters in place for privacy and security reasons, such as <br />supporting CORS rules and ensuring cookies arent readable by third parties.
</p>
<p>The <a href="https://fetch.spec.whatwg.org/#headers-class">Headers interface</a> is a simple multi-map of names to values:</p>
<div>
<pre><span>var</span> content <span>=</span> <span>"Hello World"</span><span>;</span>
</td>
</tr>
</tbody>
</table>
</div>
<p>The <code>fetch()</code> functions arguments are the same as those passed
to the
<br>
<code>Request()</code> constructor, so you may directly pass arbitrarily
complex requests to <code>fetch()</code> as discussed below.</p>
<h2>Headers</h2>
<p>Fetch introduces 3 interfaces. These are <code>Headers</code>, <code>Request</code> and
<br>
<code>Response</code>. They map directly to the underlying HTTP concepts,
but have
<br>certain visibility filters in place for privacy and security reasons,
such as
<br>supporting CORS rules and ensuring cookies arent readable by third parties.</p>
<p>The <a href="https://fetch.spec.whatwg.org/#headers-class">Headers interface</a> is
a simple multi-map of names to values:</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript"><span>var</span> content <span>=</span> <span>"Hello World"</span><span>;</span>
<span>var</span> reqHeaders <span>=</span> <span>new</span> Headers<span>(</span><span>)</span><span>;</span>
reqHeaders.<span>append</span><span>(</span><span>"Content-Type"</span><span>,</span> <span>"text/plain"</span>
reqHeaders.<span>append</span><span>(</span><span>"Content-Length"</span><span>,</span> content.<span>length</span>.<span>toString</span><span>(</span><span>)</span><span>)</span><span>;</span>
reqHeaders.<span>append</span><span>(</span><span>"X-Custom-Header"</span><span>,</span> <span>"ProcessThisImmediately"</span><span>)</span><span>;</span></pre>
</div>
<p>The same can be achieved by passing an array of arrays or a JS object literal <br />to the constructor:</p>
<div>
<pre>reqHeaders <span>=</span> <span>new</span> Headers<span>(</span><span>{</span>
</td>
</tr>
</tbody>
</table>
</div>
<p>The same can be achieved by passing an array of arrays or a JS object
literal
<br>to the constructor:</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript">reqHeaders <span>=</span> <span>new</span> Headers<span>(</span><span>{</span>
<span>"Content-Type"</span><span>:</span> <span>"text/plain"</span><span>,</span>
<span>"Content-Length"</span><span>:</span> content.<span>length</span>.<span>toString</span><span>(</span><span>)</span><span>,</span>
<span>"X-Custom-Header"</span><span>:</span> <span>"ProcessThisImmediately"</span><span>,</span>
<span>}</span><span>)</span><span>;</span></pre>
</div>
<p>The contents can be queried and retrieved:</p>
<div>
<pre>console.<span>log</span><span>(</span>reqHeaders.<span>has</span><span>(</span><span>"Content-Type"</span><span>)</span><span>)</span><span>;</span> <span>// true</span>
</td>
</tr>
</tbody>
</table>
</div>
<p>The contents can be queried and retrieved:</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript">console.<span>log</span><span>(</span>reqHeaders.<span>has</span><span>(</span><span>"Content-Type"</span><span>)</span><span>)</span><span>;</span> <span>// true</span>
console.<span>log</span><span>(</span>reqHeaders.<span>has</span><span>(</span><span>"Set-Cookie"</span><span>)</span><span>)</span><span>;</span> <span>// false</span>
reqHeaders.<span>set</span><span>(</span><span>"Content-Type"</span><span>,</span> <span>"text/html"</span><span>)</span><span>;</span>
reqHeaders.<span>append</span><span>(</span><span>"X-Custom-Header"</span><span>,</span> <span>"AnotherValue"</span><span>)</span><span>;</span>
@ -80,65 +148,163 @@ console.<span>log</span><span>(</span>reqHeaders.<span>getAll</span><span>(</spa
&nbsp;
reqHeaders.<span>delete</span><span>(</span><span>"X-Custom-Header"</span><span>)</span><span>;</span>
console.<span>log</span><span>(</span>reqHeaders.<span>getAll</span><span>(</span><span>"X-Custom-Header"</span><span>)</span><span>)</span><span>;</span> <span>// []</span></pre>
</div>
<p>Some of these operations are only useful in ServiceWorkers, but they provide <br />a much nicer API to Headers.</p>
<p>Since Headers can be sent in requests, or received in responses, and have various limitations about what information can and should be mutable, <code>Headers</code> objects have a <strong>guard</strong> property. This is not exposed to the Web, but it affects which mutation operations are allowed on the Headers object. <br />Possible values are:</p>
<ul>
<li>“none”: default.</li>
<li>“request”: guard for a Headers object obtained from a Request (<code>Request.headers</code>).</li>
<li>“request-no-cors”: guard for a Headers object obtained from a Request created <br />with mode “no-cors”.</li>
<li>“response”: naturally, for Headers obtained from Response (<code>Response.headers</code>).</li>
<li>“immutable”: Mostly used for ServiceWorkers, renders a Headers object <br />read-only.</li>
</ul>
<p>The details of how each guard affects the behaviors of the Headers object are <br />in the <a href="https://fetch.spec.whatwg.org/">specification</a>. For example, you may not append or set a “request” guarded Headers “Content-Length” header. Similarly, inserting “Set-Cookie” into a Response header is not allowed so that ServiceWorkers may not set cookies via synthesized Responses.</p>
<p>All of the Headers methods throw TypeError if <code>name</code> is not a <a href="https://fetch.spec.whatwg.org/#concept-header-name">valid HTTP Header name</a>. The mutation operations will throw TypeError if there is an immutable guard. Otherwise they fail silently. For example:</p>
<div>
<pre><span>var</span> res <span>=</span> Response.<span>error</span><span>(</span><span>)</span><span>;</span>
</td>
</tr>
</tbody>
</table>
</div>
<p>Some of these operations are only useful in ServiceWorkers, but they provide
<br>a much nicer API to Headers.</p>
<p>Since Headers can be sent in requests, or received in responses, and have
various limitations about what information can and should be mutable, <code>Headers</code> objects
have a <strong>guard</strong> property. This is not exposed to the Web, but
it affects which mutation operations are allowed on the Headers object.
<br>Possible values are:</p>
<ul>
<li>“none”: default.</li>
<li>“request”: guard for a Headers object obtained from a Request (<code>Request.headers</code>).</li>
<li>“request-no-cors”: guard for a Headers object obtained from a Request
created
<br>with mode “no-cors”.</li>
<li>“response”: naturally, for Headers obtained from Response (<code>Response.headers</code>).</li>
<li>“immutable”: Mostly used for ServiceWorkers, renders a Headers object
<br>read-only.</li>
</ul>
<p>The details of how each guard affects the behaviors of the Headers object
are
<br>in the <a href="https://fetch.spec.whatwg.org">specification</a>. For example,
you may not append or set a “request” guarded Headers “Content-Length”
header. Similarly, inserting “Set-Cookie” into a Response header is not
allowed so that ServiceWorkers may not set cookies via synthesized Responses.</p>
<p>All of the Headers methods throw TypeError if <code>name</code> is not a
<a
href="https://fetch.spec.whatwg.org/#concept-header-name">valid HTTP Header name</a>. The mutation operations will throw TypeError
if there is an immutable guard. Otherwise they fail silently. For example:</p>
<div
class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript"><span>var</span> res <span>=</span> Response.<span>error</span><span>(</span><span>)</span><span>;</span>
<span>try</span> <span>{</span>
res.<span>headers</span>.<span>set</span><span>(</span><span>"Origin"</span><span>,</span> <span>"http://mybank.com"</span><span>)</span><span>;</span>
<span>}</span> <span>catch</span><span>(</span>e<span>)</span> <span>{</span>
console.<span>log</span><span>(</span><span>"Cannot pretend to be a bank!"</span><span>)</span><span>;</span>
<span>}</span></pre>
</div>
<h2>Request</h2>
<p>The Request interface defines a request to fetch a resource over HTTP. URL, method and headers are expected, but the Request also allows specifying a body, a request mode, credentials and cache hints.</p>
<p>The simplest Request is of course, just a URL, as you may do to GET a resource.</p>
<div>
<pre><span>var</span> req <span>=</span> <span>new</span> Request<span>(</span><span>"/index.html"</span><span>)</span><span>;</span>
</td>
</tr>
</tbody>
</table>
</div>
<h2>Request</h2>
<p>The Request interface defines a request to fetch a resource over HTTP.
URL, method and headers are expected, but the Request also allows specifying
a body, a request mode, credentials and cache hints.</p>
<p>The simplest Request is of course, just a URL, as you may do to GET a
resource.</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript"><span>var</span> req <span>=</span> <span>new</span> Request<span>(</span><span>"/index.html"</span><span>)</span><span>;</span>
console.<span>log</span><span>(</span>req.<span>method</span><span>)</span><span>;</span> <span>// "GET"</span>
console.<span>log</span><span>(</span>req.<span>url</span><span>)</span><span>;</span> <span>// "http://example.com/index.html"</span></pre>
</div>
<p>You may also pass a Request to the <code>Request()</code> constructor to create a copy. <br />(This is not the same as calling the <code>clone()</code> method, which is covered in <br />the “Reading bodies” section.).</p>
<div>
<pre><span>var</span> copy <span>=</span> <span>new</span> Request<span>(</span>req<span>)</span><span>;</span>
</td>
</tr>
</tbody>
</table>
</div>
<p>You may also pass a Request to the <code>Request()</code> constructor to
create a copy.
<br>(This is not the same as calling the <code>clone()</code> method, which
is covered in
<br>the “Reading bodies” section.).</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript"><span>var</span> copy <span>=</span> <span>new</span> Request<span>(</span>req<span>)</span><span>;</span>
console.<span>log</span><span>(</span>copy.<span>method</span><span>)</span><span>;</span> <span>// "GET"</span>
console.<span>log</span><span>(</span>copy.<span>url</span><span>)</span><span>;</span> <span>// "http://example.com/index.html"</span></pre>
</div>
<p>Again, this form is probably only useful in ServiceWorkers.</p>
<p>The non-URL attributes of the <code>Request</code> can only be set by passing initial <br />values as a second argument to the constructor. This argument is a dictionary.</p>
<div>
<pre><span>var</span> uploadReq <span>=</span> <span>new</span> Request<span>(</span><span>"/uploadImage"</span><span>,</span> <span>{</span>
</td>
</tr>
</tbody>
</table>
</div>
<p>Again, this form is probably only useful in ServiceWorkers.</p>
<p>The non-URL attributes of the <code>Request</code> can only be set by passing
initial
<br>values as a second argument to the constructor. This argument is a dictionary.</p>
<div
class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript"><span>var</span> uploadReq <span>=</span> <span>new</span> Request<span>(</span><span>"/uploadImage"</span><span>,</span> <span>{</span>
method<span>:</span> <span>"POST"</span><span>,</span>
headers<span>:</span> <span>{</span>
<span>"Content-Type"</span><span>:</span> <span>"image/png"</span><span>,</span>
<span>}</span><span>,</span>
body<span>:</span> <span>"image data"</span>
<span>}</span><span>)</span><span>;</span></pre>
</div>
<p>The Requests mode is used to determine if cross-origin requests lead to valid responses, and which properties on the response are readable. Legal mode values are <code>"same-origin"</code>, <code>"no-cors"</code> (default) and <code>"cors"</code>.</p>
<p>The <code>"same-origin"</code> mode is simple, if a request is made to another origin with this mode set, the result is simply an error. You could use this to ensure that <br />a request is always being made to your origin.</p>
<div>
<pre><span>var</span> arbitraryUrl <span>=</span> document.<span>getElementById</span><span>(</span><span>"url-input"</span><span>)</span>.<span>value</span><span>;</span>
</td>
</tr>
</tbody>
</table>
</div>
<p>The Requests mode is used to determine if cross-origin requests lead
to valid responses, and which properties on the response are readable.
Legal mode values are <code>"same-origin"</code>, <code>"no-cors"</code> (default)
and <code>"cors"</code>.</p>
<p>The <code>"same-origin"</code> mode is simple, if a request is made to another
origin with this mode set, the result is simply an error. You could use
this to ensure that
<br>a request is always being made to your origin.</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript"><span>var</span> arbitraryUrl <span>=</span> document.<span>getElementById</span><span>(</span><span>"url-input"</span><span>)</span>.<span>value</span><span>;</span>
fetch<span>(</span>arbitraryUrl<span>,</span> <span>{</span> mode<span>:</span> <span>"same-origin"</span> <span>}</span><span>)</span>.<span>then</span><span>(</span><span>function</span><span>(</span>res<span>)</span> <span>{</span>
console.<span>log</span><span>(</span><span>"Response succeeded?"</span><span>,</span> res.<span>ok</span><span>)</span><span>;</span>
<span>}</span><span>,</span> <span>function</span><span>(</span>e<span>)</span> <span>{</span>
console.<span>log</span><span>(</span><span>"Please enter a same-origin URL!"</span><span>)</span><span>;</span>
<span>}</span><span>)</span><span>;</span></pre>
</div>
<p>The <code>"no-cors"</code> mode captures what the web platform does by default for scripts you import from CDNs, images hosted on other domains, and so on. First, it prevents the method from being anything other than “HEAD”, “GET” or “POST”. Second, if any ServiceWorkers intercept these requests, they may not add or override any headers except for <a href="https://fetch.spec.whatwg.org/#simple-header">these</a>. Third, JavaScript may not access any properties of the resulting Response. This ensures that ServiceWorkers do not affect the semantics of the Web and prevents security and privacy issues that could arise from leaking data across domains.</p>
<p><code>"cors"</code> mode is what youll usually use to make known cross-origin requests to access various APIs offered by other vendors. These are expected to adhere to <br />the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS">CORS protocol</a>. Only a <a href="https://fetch.spec.whatwg.org/#concept-filtered-response-cors">limited set</a> of headers is exposed in the Response, but the body is readable. For example, you could get a list of Flickrs <a href="https://www.flickr.com/services/api/flickr.interestingness.getList.html">most interesting</a> photos today like this:</p>
<div>
<pre><span>var</span> u <span>=</span> <span>new</span> URLSearchParams<span>(</span><span>)</span><span>;</span>
</td>
</tr>
</tbody>
</table>
</div>
<p>The <code>"no-cors"</code> mode captures what the web platform does by default
for scripts you import from CDNs, images hosted on other domains, and so
on. First, it prevents the method from being anything other than “HEAD”,
“GET” or “POST”. Second, if any ServiceWorkers intercept these requests,
they may not add or override any headers except for <a href="https://fetch.spec.whatwg.org/#simple-header">these</a>.
Third, JavaScript may not access any properties of the resulting Response.
This ensures that ServiceWorkers do not affect the semantics of the Web
and prevents security and privacy issues that could arise from leaking
data across domains.</p>
<p><code>"cors"</code> mode is what youll usually use to make known cross-origin
requests to access various APIs offered by other vendors. These are expected
to adhere to
<br>the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS">CORS protocol</a>.
Only a <a href="https://fetch.spec.whatwg.org/#concept-filtered-response-cors">limited set</a> of
headers is exposed in the Response, but the body is readable. For example,
you could get a list of Flickrs <a href="https://www.flickr.com/services/api/flickr.interestingness.getList.html">most interesting</a> photos
today like this:</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript"><span>var</span> u <span>=</span> <span>new</span> URLSearchParams<span>(</span><span>)</span><span>;</span>
u.<span>append</span><span>(</span><span>'method'</span><span>,</span> <span>'flickr.interestingness.getList'</span><span>)</span><span>;</span>
u.<span>append</span><span>(</span><span>'api_key'</span><span>,</span> <span>'&lt;insert api key here&gt;'</span><span>)</span><span>;</span>
u.<span>append</span><span>(</span><span>'format'</span><span>,</span> <span>'json'</span><span>)</span><span>;</span>
@ -156,89 +322,180 @@ apiCall.<span>then</span><span>(</span><span>function</span><span>(</span>respon
console.<span>log</span><span>(</span>photo.<span>title</span><span>)</span><span>;</span>
<span>}</span><span>)</span><span>;</span>
<span>}</span><span>)</span><span>;</span></pre>
</div>
<p>You may not read out the “Date” header since Flickr does not allow it via <br />
<code>Access-Control-Expose-Headers</code>.
</p>
<div>
<pre>response.<span>headers</span>.<span>get</span><span>(</span><span>"Date"</span><span>)</span><span>;</span> <span>// null</span></pre>
</div>
<p>The <code>credentials</code> enumeration determines if cookies for the other domain are <br />sent to cross-origin requests. This is similar to XHRs <code>withCredentials</code>
<br />flag, but tri-valued as <code>"omit"</code> (default), <code>"same-origin"</code> and <code>"include"</code>.
</p>
<p>The Request object will also give the ability to offer caching hints to the user-agent. This is currently undergoing some <a href="https://github.com/slightlyoff/ServiceWorker/issues/585">security review</a>. Firefox exposes the attribute, but it has no effect.</p>
<p>Requests have two read-only attributes that are relevant to ServiceWorkers <br />intercepting them. There is the string <code>referrer</code>, which is set by the UA to be <br />the referrer of the Request. This may be an empty string. The other is <br />
<code>context</code> which is a rather <a href="https://fetch.spec.whatwg.org/#requestcredentials">large enumeration</a> defining what sort of resource is being fetched. This could be “image” if the request is from an &lt;img&gt;tag in the controlled document, “worker” if it is an attempt to load a worker script, and so on. When used with the <code>fetch()</code> function, it is “fetch”.
</p>
<h2>Response</h2>
<p><code>Response</code> instances are returned by calls to <code>fetch()</code>. They can also be created by JS, but this is only useful in ServiceWorkers.</p>
<p>We have already seen some attributes of Response when we looked at <code>fetch()</code>. The most obvious candidates are <code>status</code>, an integer (default value 200) and <code>statusText</code> (default value “OK”), which correspond to the HTTP status code and reason. The <code>ok</code> attribute is just a shorthand for checking that <code>status</code> is in the range 200-299 inclusive.</p>
<p><code>headers</code> is the Responses Headers object, with guard “response”. The <code>url</code> attribute reflects the URL of the corresponding request.</p>
<p>Response also has a <code>type</code>, which is “basic”, “cors”, “default”, “error” or <br />“opaque”.</p>
<ul>
<li><code>"basic"</code>: normal, same origin response, with all headers exposed except <br />“Set-Cookie” and “Set-Cookie2″.</li>
<li><code>"cors"</code>: response was received from a valid cross-origin request. <a href="https://fetch.spec.whatwg.org/#concept-filtered-response-cors">Certain headers and the body</a>may be accessed.</li>
<li><code>"error"</code>: network error. No useful information describing the error is available. The Responses status is 0, headers are empty and immutable. This is the type for a Response obtained from <code>Response.error()</code>.</li>
<li><code>"opaque"</code>: response for “no-cors” request to cross-origin resource. <a href="https://fetch.spec.whatwg.org/#concept-filtered-response-opaque">Severely<br /> restricted</a>
</li>
</ul>
<p>The “error” type results in the <code>fetch()</code> Promise rejecting with TypeError.</p>
<p>There are certain attributes that are useful only in a ServiceWorker scope. The <br />idiomatic way to return a Response to an intercepted request in ServiceWorkers is:</p>
<div>
<pre>addEventListener<span>(</span><span>'fetch'</span><span>,</span> <span>function</span><span>(</span>event<span>)</span> <span>{</span>
</td>
</tr>
</tbody>
</table>
</div>
<p>You may not read out the “Date” header since Flickr does not allow it
via
<br>
<code>Access-Control-Expose-Headers</code>.</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript">response.<span>headers</span>.<span>get</span><span>(</span><span>"Date"</span><span>)</span><span>;</span> <span>// null</span></pre>
</td>
</tr>
</tbody>
</table>
</div>
<p>The <code>credentials</code> enumeration determines if cookies for the other
domain are
<br>sent to cross-origin requests. This is similar to XHRs <code>withCredentials</code>
<br>flag, but tri-valued as <code>"omit"</code> (default), <code>"same-origin"</code> and <code>"include"</code>.</p>
<p>The Request object will also give the ability to offer caching hints to
the user-agent. This is currently undergoing some <a href="https://github.com/slightlyoff/ServiceWorker/issues/585">security review</a>.
Firefox exposes the attribute, but it has no effect.</p>
<p>Requests have two read-only attributes that are relevant to ServiceWorkers
<br>intercepting them. There is the string <code>referrer</code>, which is
set by the UA to be
<br>the referrer of the Request. This may be an empty string. The other is
<br>
<code>context</code> which is a rather <a href="https://fetch.spec.whatwg.org/#requestcredentials">large enumeration</a> defining
what sort of resource is being fetched. This could be “image” if the request
is from an
<img>tag in the controlled document, “worker” if it is an attempt to load a
worker script, and so on. When used with the <code>fetch()</code> function,
it is “fetch”.</p>
<h2>Response</h2>
<p><code>Response</code> instances are returned by calls to <code>fetch()</code>.
They can also be created by JS, but this is only useful in ServiceWorkers.</p>
<p>We have already seen some attributes of Response when we looked at <code>fetch()</code>.
The most obvious candidates are <code>status</code>, an integer (default
value 200) and <code>statusText</code> (default value “OK”), which correspond
to the HTTP status code and reason. The <code>ok</code> attribute is just
a shorthand for checking that <code>status</code> is in the range 200-299
inclusive.</p>
<p><code>headers</code> is the Responses Headers object, with guard “response”.
The <code>url</code> attribute reflects the URL of the corresponding request.</p>
<p>Response also has a <code>type</code>, which is “basic”, “cors”, “default”,
“error” or
<br>“opaque”.</p>
<ul>
<li><code>"basic"</code>: normal, same origin response, with all headers exposed
except
<br>“Set-Cookie” and “Set-Cookie2″.</li>
<li><code>"cors"</code>: response was received from a valid cross-origin request.
<a
href="https://fetch.spec.whatwg.org/#concept-filtered-response-cors">Certain headers and the body</a>may be accessed.</li>
<li><code>"error"</code>: network error. No useful information describing
the error is available. The Responses status is 0, headers are empty and
immutable. This is the type for a Response obtained from <code>Response.error()</code>.</li>
<li><code>"opaque"</code>: response for “no-cors” request to cross-origin
resource. <a href="https://fetch.spec.whatwg.org/#concept-filtered-response-opaque">Severely<br>
restricted</a>
</li>
</ul>
<p>The “error” type results in the <code>fetch()</code> Promise rejecting with
TypeError.</p>
<p>There are certain attributes that are useful only in a ServiceWorker scope.
The
<br>idiomatic way to return a Response to an intercepted request in ServiceWorkers
is:</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript">addEventListener<span>(</span><span>'fetch'</span><span>,</span> <span>function</span><span>(</span>event<span>)</span> <span>{</span>
event.<span>respondWith</span><span>(</span><span>new</span> Response<span>(</span><span>"Response body"</span><span>,</span> <span>{</span>
headers<span>:</span> <span>{</span> <span>"Content-Type"</span> <span>:</span> <span>"text/plain"</span> <span>}</span>
<span>}</span><span>)</span><span>;</span>
<span>}</span><span>)</span><span>;</span></pre>
</div>
<p>As you can see, Response has a two argument constructor, where both arguments are optional. The first argument is a body initializer, and the second is a dictionary to set the <code>status</code>, <code>statusText</code> and <code>headers</code>.</p>
<p>The static method <code>Response.error()</code> simply returns an error response. Similarly, <code>Response.redirect(url, status)</code> returns a Response resulting in <br />a redirect to <code>url</code>.</p>
<h2>Dealing with bodies</h2>
<p>Both Requests and Responses may contain body data. Weve been glossing over it because of the various data types body may contain, but we will cover it in detail now.</p>
<p>A body is an instance of any of the following types.</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer">ArrayBuffer</a>
</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView">ArrayBufferView</a> (Uint8Array and friends)</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>/ <a href="https://developer.mozilla.org/en-US/docs/Web/API/File">File</a>
</li>
<li>string</li>
<li><a href="https://url.spec.whatwg.org/#interface-urlsearchparams">URLSearchParams</a>
</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> currently not supported by either Gecko or Blink. Firefox expects to ship this in version 39 along with the rest of Fetch.</li>
</ul>
<p>In addition, Request and Response both offer the following methods to extract their body. These all return a Promise that is eventually resolved with the actual content.</p>
<ul>
<li><code>arrayBuffer()</code>
</li>
<li><code>blob()</code>
</li>
<li><code>json()</code>
</li>
<li><code>text()</code>
</li>
<li><code>formData()</code>
</li>
</ul>
<p>This is a significant improvement over XHR in terms of ease of use of non-text data!</p>
<p>Request bodies can be set by passing <code>body</code> parameters:</p>
<div>
<pre><span>var</span> form <span>=</span> <span>new</span> FormData<span>(</span>document.<span>getElementById</span><span>(</span><span>'login-form'</span><span>)</span><span>)</span><span>;</span>
</td>
</tr>
</tbody>
</table>
</div>
<p>As you can see, Response has a two argument constructor, where both arguments
are optional. The first argument is a body initializer, and the second
is a dictionary to set the <code>status</code>, <code>statusText</code> and <code>headers</code>.</p>
<p>The static method <code>Response.error()</code> simply returns an error
response. Similarly, <code>Response.redirect(url, status)</code> returns
a Response resulting in
<br>a redirect to <code>url</code>.</p>
<h2>Dealing with bodies</h2>
<p>Both Requests and Responses may contain body data. Weve been glossing
over it because of the various data types body may contain, but we will
cover it in detail now.</p>
<p>A body is an instance of any of the following types.</p>
<p>In addition, Request and Response both offer the following methods to
extract their body. These all return a Promise that is eventually resolved
with the actual content.</p>
<ul>
<li><code>arrayBuffer()</code>
</li>
<li><code>blob()</code>
</li>
<li><code>json()</code>
</li>
<li><code>text()</code>
</li>
<li><code>formData()</code>
</li>
</ul>
<p>This is a significant improvement over XHR in terms of ease of use of
non-text data!</p>
<p>Request bodies can be set by passing <code>body</code> parameters:</p>
<div
class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript"><span>var</span> form <span>=</span> <span>new</span> FormData<span>(</span>document.<span>getElementById</span><span>(</span><span>'login-form'</span><span>)</span><span>)</span><span>;</span>
fetch<span>(</span><span>"/login"</span><span>,</span> <span>{</span>
method<span>:</span> <span>"POST"</span><span>,</span>
body<span>:</span> form
<span>}</span><span>)</span></pre>
</td>
</tr>
</tbody>
</table>
</div>
<p>Responses take the first argument as the body.</p>
<div>
<pre><span>var</span> res <span>=</span> <span>new</span> Response<span>(</span><span>new</span> File<span>(</span><span>[</span><span>"chunk"</span><span>,</span> <span>"chunk"</span><span>]</span><span>,</span> <span>"archive.zip"</span><span>,</span>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript"><span>var</span> res <span>=</span> <span>new</span> Response<span>(</span><span>new</span> File<span>(</span><span>[</span><span>"chunk"</span><span>,</span> <span>"chunk"</span><span>]</span><span>,</span> <span>"archive.zip"</span><span>,</span>
<span>{</span> type<span>:</span> <span>"application/zip"</span> <span>}</span><span>)</span><span>)</span><span>;</span></pre>
</td>
</tr>
</tbody>
</table>
</div>
<p>Both Request and Response (and by extension the <code>fetch()</code> function), will try to intelligently <a href="https://fetch.spec.whatwg.org/#concept-bodyinit-extract">determine the content type</a>. Request will also automatically set a “Content-Type” header if none is set in the dictionary.</p>
<h3>Streams and cloning</h3>
<p>It is important to realise that Request and Response bodies can only be read once! Both interfaces have a boolean attribute <code>bodyUsed</code> to determine if it is safe to read or not.</p>
<div>
<pre><span>var</span> res <span>=</span> <span>new</span> Response<span>(</span><span>"one time use"</span><span>)</span><span>;</span>
<p>Both Request and Response (and by extension the <code>fetch()</code> function),
will try to intelligently <a href="https://fetch.spec.whatwg.org/#concept-bodyinit-extract">determine the content type</a>.
Request will also automatically set a “Content-Type” header if none is
set in the dictionary.</p>
<h3>Streams and cloning</h3>
<p>It is important to realise that Request and Response bodies can only be
read once! Both interfaces have a boolean attribute <code>bodyUsed</code> to
determine if it is safe to read or not.</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript"><span>var</span> res <span>=</span> <span>new</span> Response<span>(</span><span>"one time use"</span><span>)</span><span>;</span>
console.<span>log</span><span>(</span>res.<span>bodyUsed</span><span>)</span><span>;</span> <span>// false</span>
res.<span>text</span><span>(</span><span>)</span>.<span>then</span><span>(</span><span>function</span><span>(</span>v<span>)</span> <span>{</span>
console.<span>log</span><span>(</span>res.<span>bodyUsed</span><span>)</span><span>;</span> <span>// true</span>
@ -248,12 +505,30 @@ console.<span>log</span><span>(</span>res.<span>bodyUsed</span><span>)</span><sp
res.<span>text</span><span>(</span><span>)</span>.<span>catch</span><span>(</span><span>function</span><span>(</span>e<span>)</span> <span>{</span>
console.<span>log</span><span>(</span><span>"Tried to read already consumed Response"</span><span>)</span><span>;</span>
<span>}</span><span>)</span><span>;</span></pre>
</td>
</tr>
</tbody>
</table>
</div>
<p>This decision allows easing the transition to an eventual <a href="https://streams.spec.whatwg.org/">stream-based</a> Fetch API. The intention is to let applications consume data as it arrives, allowing for JavaScript to deal with larger files like videos, and perform things like compression and editing on the fly.</p>
<p>Often, youll want access to the body multiple times. For example, you can use the upcoming <a href="http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#cache-objects">Cache API</a> to store Requests and Responses for offline use, and Cache requires bodies to be available for reading.</p>
<p>So how do you read out the body multiple times within such constraints? The API provides a <code>clone()</code> method on the two interfaces. This will return a clone of the object, with a new body. <code>clone()</code> MUST be called before the body of the corresponding object has been used. That is, <code>clone()</code> first, read later.</p>
<div>
<pre>addEventListener<span>(</span><span>'fetch'</span><span>,</span> <span>function</span><span>(</span>evt<span>)</span> <span>{</span>
<p>This decision allows easing the transition to an eventual <a href="https://streams.spec.whatwg.org/">stream-based</a> Fetch
API. The intention is to let applications consume data as it arrives, allowing
for JavaScript to deal with larger files like videos, and perform things
like compression and editing on the fly.</p>
<p>Often, youll want access to the body multiple times. For example, you
can use the upcoming <a href="http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#cache-objects">Cache API</a> to
store Requests and Responses for offline use, and Cache requires bodies
to be available for reading.</p>
<p>So how do you read out the body multiple times within such constraints?
The API provides a <code>clone()</code> method on the two interfaces. This
will return a clone of the object, with a new body. <code>clone()</code> MUST
be called before the body of the corresponding object has been used. That
is, <code>clone()</code> first, read later.</p>
<div class="wp_syntax">
<table>
<tbody>
<tr>
<td class="code"><pre class="javascript">addEventListener<span>(</span><span>'fetch'</span><span>,</span> <span>function</span><span>(</span>evt<span>)</span> <span>{</span>
<span>var</span> sheep <span>=</span> <span>new</span> Response<span>(</span><span>"Dolly"</span><span>)</span><span>;</span>
console.<span>log</span><span>(</span>sheep.<span>bodyUsed</span><span>)</span><span>;</span> <span>// false</span>
<span>var</span> clone <span>=</span> sheep.<span>clone</span><span>(</span><span>)</span><span>;</span>
@ -267,13 +542,28 @@ res.<span>text</span><span>(</span><span>)</span>.<span>catch</span><span>(</spa
<span>return</span> sheep<span>;</span>
<span>}</span><span>)</span><span>;</span>
<span>}</span><span>)</span><span>;</span></pre>
</td>
</tr>
</tbody>
</table>
</div>
<h2>Future improvements</h2>
<p>Along with the transition to streams, Fetch will eventually have the ability to abort running <code>fetch()</code>es and some way to report the progress of a fetch. These are provided by XHR, but are a little tricky to fit in the Promise-based nature of the Fetch API.</p>
<p>You can contribute to the evolution of this API by participating in discussions on the <a href="https://whatwg.org/mailing-list">WHATWG mailing list</a> and in the issues in the <a href="https://www.w3.org/Bugs/Public/buglist.cgi?product=WHATWG&amp;component=Fetch&amp;resolution=---">Fetch</a> and <a href="https://github.com/slightlyoff/ServiceWorker/issues">ServiceWorker</a>specifications.</p>
<h2>Future improvements</h2>
<p>Along with the transition to streams, Fetch will eventually have the ability
to abort running <code>fetch()</code>es and some way to report the progress
of a fetch. These are provided by XHR, but are a little tricky to fit in
the Promise-based nature of the Fetch API.</p>
<p>You can contribute to the evolution of this API by participating in discussions
on the <a href="https://whatwg.org/mailing-list">WHATWG mailing list</a> and
in the issues in the <a href="https://www.w3.org/Bugs/Public/buglist.cgi?product=WHATWG&amp;component=Fetch&amp;resolution=---">Fetch</a> and
<a
href="https://github.com/slightlyoff/ServiceWorker/issues">ServiceWorker</a>specifications.</p>
<p>For a better web!</p>
<p><em>The author would like to thank Andrea Marchesini, Anne van Kesteren and Ben<br /> Kelly for helping with the specification and implementation.</em>
<p><em>The author would like to thank Andrea Marchesini, Anne van Kesteren and Ben<br>
Kelly for helping with the specification and implementation.</em>
</p>
</article>
</div>
</div>
</article>
</div>

@ -2,30 +2,30 @@
<html lang="en-US" id="hacks-mozilla-org">
<head>
<meta name="viewport" content="width=device-width"/>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width">
<meta charset="UTF-8">
<!-- OpenGraph metadata -->
<meta property="og:site_name" content="Mozilla Hacks the Web developer blog"/>
<meta property="og:title" content="This API is so Fetching!"/>
<meta property="og:url" content="https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/"/>
<meta property="og:description" content="For more than a decade the Web has used XMLHttpRequest (XHR) to achieve asynchronous requests in JavaScript. While very useful, XHR is not a very ..."/>
<meta property="og:site_name" content="Mozilla Hacks the Web developer blog">
<meta property="og:title" content="This API is so Fetching!">
<meta property="og:url" content="https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/">
<meta property="og:description" content="For more than a decade the Web has used XMLHttpRequest (XHR) to achieve asynchronous requests in JavaScript. While very useful, XHR is not a very ...">
<!--[if IE]>
<meta name="MSSmartTagsPreventParsing" content="true"/>
<meta http-equiv="imagetoolbar" content="no"/>
<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
<meta name="MSSmartTagsPreventParsing" content="true">
<meta http-equiv="imagetoolbar" content="no">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link rel="shortcut icon" type="image/ico" href="https://hacks.mozilla.org/wp-content/themes/Hacks2013/favicon.ico"/>
<link rel="home" href="/"/>
<link rel="copyright" href="#copyright"/>
<link rel="stylesheet" type="text/css" media="screen,projection" href="https://hacks.mozilla.org/wp-content/themes/Hacks2013/style.css"/>
<link rel="stylesheet" type="text/css" media="print,handheld" href="https://hacks.mozilla.org/wp-content/themes/Hacks2013/css/print.css"/>
<link rel="stylesheet" type="text/css" media="all" href="https://www.mozilla.org/tabzilla/media/css/tabzilla.css"/>
<link rel="stylesheet" type="text/css" media="all" href="https://hacks.mozilla.org/wp-content/themes/Hacks2013/css/socialshare.css"/>
<link rel="shortcut icon" type="image/ico" href="https://hacks.mozilla.org/wp-content/themes/Hacks2013/favicon.ico">
<link rel="home" href="/">
<link rel="copyright" href="#copyright">
<link rel="stylesheet" type="text/css" media="screen,projection" href="https://hacks.mozilla.org/wp-content/themes/Hacks2013/style.css">
<link rel="stylesheet" type="text/css" media="print,handheld" href="https://hacks.mozilla.org/wp-content/themes/Hacks2013/css/print.css">
<link rel="stylesheet" type="text/css" media="all" href="https://www.mozilla.org/tabzilla/media/css/tabzilla.css">
<link rel="stylesheet" type="text/css" media="all" href="https://hacks.mozilla.org/wp-content/themes/Hacks2013/css/socialshare.css">
<link rel="alternate" type="application/rss+xml" title="Mozilla Hacks the Web developer blog RSS Feed"
href="https://hacks.mozilla.org/feed/"/>
<link rel="pingback" href="https://hacks.mozilla.org/xmlrpc.php"/>
<link rel="canonical" href="https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/"/>
href="https://hacks.mozilla.org/feed/">
<link rel="pingback" href="https://hacks.mozilla.org/xmlrpc.php">
<link rel="canonical" href="https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/">
<!--[if lte IE 7]>
<link rel="stylesheet" type="text/css" media="all" href="https://hacks.mozilla.org/wp-content/themes/Hacks2013/css/ie7.css"
/>
@ -49,21 +49,21 @@
})(document, 'script');</script>
<link rel="alternate" type="application/rss+xml"
title="Mozilla Hacks - the Web developer blog » This API is so Fetching! Comments Feed"
href="https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/feed/"/>
href="https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/feed/">
<link rel="stylesheet" id="wp-syntax-css-css" href="https://hacks.mozilla.org/wp-content/plugins/wp-syntax/css/wp-syntax.css?ver=1.0"
type="text/css" media="all"/>
type="text/css" media="all">
<script type="text/javascript" src="https://hacks.mozilla.org/wp-includes/js/jquery/jquery.js?ver=1.11.1"></script>
<script type="text/javascript" src="https://hacks.mozilla.org/wp-includes/js/jquery/jquery-migrate.min.js?ver=1.2.1"></script>
<script type="text/javascript" src="https://hacks.mozilla.org/wp-content/themes/Hacks2013/js/fc-checkcomment.js?ver=4.1"></script>
<script type="text/javascript" src="https://hacks.mozilla.org/wp-content/themes/Hacks2013/js/analytics.js?ver=4.1"></script>
<script type="text/javascript" src="https://hacks.mozilla.org/wp-content/themes/Hacks2013/js/socialshare.min.js?ver=4.1"></script>
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="https://hacks.mozilla.org/xmlrpc.php?rsd"/>
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="https://hacks.mozilla.org/wp-includes/wlwmanifest.xml"/>
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="https://hacks.mozilla.org/xmlrpc.php?rsd">
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="https://hacks.mozilla.org/wp-includes/wlwmanifest.xml">
<link rel="prev" title="Ruby support in Firefox Developer Edition 38"
href="https://hacks.mozilla.org/2015/03/ruby-support-in-firefox-developer-edition-38/"/>
<link rel="next" title="Optimising SVG images" href="https://hacks.mozilla.org/2015/03/optimising-svg-images/"/>
<link rel="canonical" href="https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/"/>
<link rel="shortlink" href="https://hacks.mozilla.org/?p=28260"/>
href="https://hacks.mozilla.org/2015/03/ruby-support-in-firefox-developer-edition-38/">
<link rel="next" title="Optimising SVG images" href="https://hacks.mozilla.org/2015/03/optimising-svg-images/">
<link rel="canonical" href="https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/">
<link rel="shortlink" href="https://hacks.mozilla.org/?p=28260">
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-36116321-4']);
@ -90,7 +90,7 @@
role="search">
<p>
<input type="search" placeholder="Search hacks.mozilla.org" value="" name="s"
id="s"/>
id="s">
<button type="submit">Go</button>
</p>
</form>
@ -237,19 +237,19 @@ by
</div>
<p>The <code>fetch()</code> functions arguments are the same as those passed
to the
<br/>
<br>
<code>Request()</code> constructor, so you may directly pass arbitrarily
complex requests to <code>fetch()</code> as discussed below.</p>
<h2>Headers</h2>
<p>Fetch introduces 3 interfaces. These are <code>Headers</code>, <code>Request</code> and
<br/>
<br>
<code>Response</code>. They map directly to the underlying HTTP concepts,
but have
<br/>certain visibility filters in place for privacy and security reasons,
<br>certain visibility filters in place for privacy and security reasons,
such as
<br/>supporting CORS rules and ensuring cookies arent readable by third parties.</p>
<br>supporting CORS rules and ensuring cookies arent readable by third parties.</p>
<p>The <a href="https://fetch.spec.whatwg.org/#headers-class">Headers interface</a> is
a simple multi-map of names to values:</p>
<div class="wp_syntax">
@ -268,7 +268,7 @@ reqHeaders.<span style="color: #660066;">append</span><span style="color: #00990
</div>
<p>The same can be achieved by passing an array of arrays or a JS object
literal
<br/>to the constructor:</p>
<br>to the constructor:</p>
<div class="wp_syntax">
<table>
<tbody>
@ -304,25 +304,25 @@ console.<span style="color: #660066;">log</span><span style="color: #009900;">(<
</table>
</div>
<p>Some of these operations are only useful in ServiceWorkers, but they provide
<br/>a much nicer API to Headers.</p>
<br>a much nicer API to Headers.</p>
<p>Since Headers can be sent in requests, or received in responses, and have
various limitations about what information can and should be mutable, <code>Headers</code> objects
have a <strong>guard</strong> property. This is not exposed to the Web, but
it affects which mutation operations are allowed on the Headers object.
<br/>Possible values are:</p>
<br>Possible values are:</p>
<ul>
<li>“none”: default.</li>
<li>“request”: guard for a Headers object obtained from a Request (<code>Request.headers</code>).</li>
<li>“request-no-cors”: guard for a Headers object obtained from a Request
created
<br/>with mode “no-cors”.</li>
<br>with mode “no-cors”.</li>
<li>“response”: naturally, for Headers obtained from Response (<code>Response.headers</code>).</li>
<li>“immutable”: Mostly used for ServiceWorkers, renders a Headers object
<br/>read-only.</li>
<br>read-only.</li>
</ul>
<p>The details of how each guard affects the behaviors of the Headers object
are
<br/>in the <a href="https://fetch.spec.whatwg.org">specification</a>. For example,
<br>in the <a href="https://fetch.spec.whatwg.org">specification</a>. For example,
you may not append or set a “request” guarded Headers “Content-Length”
header. Similarly, inserting “Set-Cookie” into a Response header is not
allowed so that ServiceWorkers may not set cookies via synthesized Responses.</p>
@ -368,9 +368,9 @@ console.<span style="color: #660066;">log</span><span style="color: #009900;">(<
</div>
<p>You may also pass a Request to the <code>Request()</code> constructor to
create a copy.
<br/>(This is not the same as calling the <code>clone()</code> method, which
<br>(This is not the same as calling the <code>clone()</code> method, which
is covered in
<br/>the “Reading bodies” section.).</p>
<br>the “Reading bodies” section.).</p>
<div class="wp_syntax">
<table>
<tbody>
@ -386,7 +386,7 @@ console.<span style="color: #660066;">log</span><span style="color: #009900;">(<
<p>Again, this form is probably only useful in ServiceWorkers.</p>
<p>The non-URL attributes of the <code>Request</code> can only be set by passing
initial
<br/>values as a second argument to the constructor. This argument is a dictionary.</p>
<br>values as a second argument to the constructor. This argument is a dictionary.</p>
<div
class="wp_syntax">
<table>
@ -411,7 +411,7 @@ console.<span style="color: #660066;">log</span><span style="color: #009900;">(<
<p>The <code>"same-origin"</code> mode is simple, if a request is made to another
origin with this mode set, the result is simply an error. You could use
this to ensure that
<br/>a request is always being made to your origin.</p>
<br>a request is always being made to your origin.</p>
<div class="wp_syntax">
<table>
<tbody>
@ -439,7 +439,7 @@ fetch<span style="color: #009900;">(</span>arbitraryUrl<span style="color: #3399
<p><code>"cors"</code> mode is what youll usually use to make known cross-origin
requests to access various APIs offered by other vendors. These are expected
to adhere to
<br/>the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS">CORS protocol</a>.
<br>the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS">CORS protocol</a>.
Only a <a href="https://fetch.spec.whatwg.org/#concept-filtered-response-cors">limited set</a> of
headers is exposed in the Response, but the body is readable. For example,
you could get a list of Flickrs <a href="https://www.flickr.com/services/api/flickr.interestingness.getList.html">most interesting</a> photos
@ -473,7 +473,7 @@ apiCall.<span style="color: #660066;">then</span><span style="color: #009900;">(
</div>
<p>You may not read out the “Date” header since Flickr does not allow it
via
<br/>
<br>
<code>Access-Control-Expose-Headers</code>.</p>
<div class="wp_syntax">
<table>
@ -487,20 +487,20 @@ apiCall.<span style="color: #660066;">then</span><span style="color: #009900;">(
</div>
<p>The <code>credentials</code> enumeration determines if cookies for the other
domain are
<br/>sent to cross-origin requests. This is similar to XHRs <code>withCredentials</code>
<br/>flag, but tri-valued as <code>"omit"</code> (default), <code>"same-origin"</code> and <code>"include"</code>.</p>
<br>sent to cross-origin requests. This is similar to XHRs <code>withCredentials</code>
<br>flag, but tri-valued as <code>"omit"</code> (default), <code>"same-origin"</code> and <code>"include"</code>.</p>
<p>The Request object will also give the ability to offer caching hints to
the user-agent. This is currently undergoing some <a href="https://github.com/slightlyoff/ServiceWorker/issues/585">security review</a>.
Firefox exposes the attribute, but it has no effect.</p>
<p>Requests have two read-only attributes that are relevant to ServiceWorkers
<br/>intercepting them. There is the string <code>referrer</code>, which is
<br>intercepting them. There is the string <code>referrer</code>, which is
set by the UA to be
<br/>the referrer of the Request. This may be an empty string. The other is
<br/>
<br>the referrer of the Request. This may be an empty string. The other is
<br>
<code>context</code> which is a rather <a href="https://fetch.spec.whatwg.org/#requestcredentials">large enumeration</a> defining
what sort of resource is being fetched. This could be “image” if the request
is from an
&lt;img&gt;tag in the controlled document, “worker” if it is an attempt to load a
<img>tag in the controlled document, “worker” if it is an attempt to load a
worker script, and so on. When used with the <code>fetch()</code> function,
it is “fetch”.</p>
@ -518,11 +518,11 @@ apiCall.<span style="color: #660066;">then</span><span style="color: #009900;">(
The <code>url</code> attribute reflects the URL of the corresponding request.</p>
<p>Response also has a <code>type</code>, which is “basic”, “cors”, “default”,
“error” or
<br/>“opaque”.</p>
<br>“opaque”.</p>
<ul>
<li><code>"basic"</code>: normal, same origin response, with all headers exposed
except
<br/>“Set-Cookie” and “Set-Cookie2″.</li>
<br>“Set-Cookie” and “Set-Cookie2″.</li>
<li><code>"cors"</code>: response was received from a valid cross-origin request.
<a
href="https://fetch.spec.whatwg.org/#concept-filtered-response-cors">Certain headers and the body</a>may be accessed.</li>
@ -530,7 +530,7 @@ apiCall.<span style="color: #660066;">then</span><span style="color: #009900;">(
the error is available. The Responses status is 0, headers are empty and
immutable. This is the type for a Response obtained from <code>Response.error()</code>.</li>
<li><code>"opaque"</code>: response for “no-cors” request to cross-origin
resource. <a href="https://fetch.spec.whatwg.org/#concept-filtered-response-opaque">Severely<br/>
resource. <a href="https://fetch.spec.whatwg.org/#concept-filtered-response-opaque">Severely<br>
restricted</a>
</li>
</ul>
@ -538,7 +538,7 @@ apiCall.<span style="color: #660066;">then</span><span style="color: #009900;">(
TypeError.</p>
<p>There are certain attributes that are useful only in a ServiceWorker scope.
The
<br/>idiomatic way to return a Response to an intercepted request in ServiceWorkers
<br>idiomatic way to return a Response to an intercepted request in ServiceWorkers
is:</p>
<div class="wp_syntax">
<table>
@ -560,7 +560,7 @@ apiCall.<span style="color: #660066;">then</span><span style="color: #009900;">(
<p>The static method <code>Response.error()</code> simply returns an error
response. Similarly, <code>Response.redirect(url, status)</code> returns
a Response resulting in
<br/>a redirect to <code>url</code>.</p>
<br>a redirect to <code>url</code>.</p>
<h2>Dealing with bodies</h2>
@ -707,7 +707,7 @@ res.<span style="color: #660066;">text</span><span style="color: #009900;">(</sp
<a
href="https://github.com/slightlyoff/ServiceWorker/issues">ServiceWorker</a>specifications.</p>
<p>For a better web!</p>
<p><em>The author would like to thank Andrea Marchesini, Anne van Kesteren and Ben<br/>
<p><em>The author would like to thank Andrea Marchesini, Anne van Kesteren and Ben<br>
Kelly for helping with the specification and implementation.</em>
</p>
<footer class="entry-meta">
@ -733,7 +733,7 @@ on
</header>
<ol id="comment-list" class="hfeed av">
<li id="comment-17303" class="comment even thread-even depth-1 hentry">
<p class="entry-title vcard"> <span class="photo"><img alt="" src="https://secure.gravatar.com/avatar/cc6f7a60f71adaa81b7bc798ebc71be1?s=48&amp;d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D48&amp;r=G" class="avatar avatar-48 photo" height="48" width="48"/></span>
<p class="entry-title vcard"> <span class="photo"><img alt="" src="https://secure.gravatar.com/avatar/cc6f7a60f71adaa81b7bc798ebc71be1?s=48&amp;d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D48&amp;r=G" class="avatar avatar-48 photo" height="48" width="48"></span>
<cite
class="author fn">Alexander Petrov</cite> <span class="comment-meta">wrote on <a href="https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/comment-page-1/#comment-17303" rel="bookmark" title="Permanent link to this comment by Alexander Petrov"><abbr class="published" title="2015-03-11">March 11th, 2015</abbr> at 02:57</a>:</span>
@ -749,7 +749,7 @@ on
<li id="comment-17305" class="comment byuser comment-author-nmarathemozilla-com bypostauthor odd alt depth-2 hentry">
<p class="entry-title vcard"> <a href="http://blog.nikhilism.com" class="url" rel="nofollow external"
title="http://blog.nikhilism.com">
<span class="photo"><img alt="" src="https://secure.gravatar.com/avatar/098bc26c1d2d2b425b258cff67044ac1?s=48&amp;d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D48&amp;r=G" class="avatar avatar-48 photo" height="48" width="48"/></span> <cite class="author fn">Nikhil Marathe</cite>
<span class="photo"><img alt="" src="https://secure.gravatar.com/avatar/098bc26c1d2d2b425b258cff67044ac1?s=48&amp;d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D48&amp;r=G" class="avatar avatar-48 photo" height="48" width="48"></span> <cite class="author fn">Nikhil Marathe</cite>
</a>
<span class="comment-meta">wrote on <a href="https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/comment-page-1/#comment-17305" rel="bookmark" title="Permanent link to this comment by Nikhil Marathe"><abbr class="published" title="2015-03-11">March 11th, 2015</abbr> at 08:00</a>:</span>
@ -784,21 +784,21 @@ on
<li id="cmt-name">
<label for="author">Your name <abbr title="(required)">*</abbr>
</label>
<input type="text" name="author" id="author" size="25" required="" aria-required="true"/>
<input type="text" name="author" id="author" size="25" required="" aria-required="true">
</li>
<li id="cmt-email">
<label for="email">Your e-mail <abbr title="(required)">*</abbr>
</label>
<input type="email" name="email" id="email" size="25" required="" aria-required="true"/>
<input type="email" name="email" id="email" size="25" required="" aria-required="true">
</li>
<li id="cmt-web">
<label for="url">Your website</label>
<input type="url" name="url" id="url" size="25"/>
<input type="url" name="url" id="url" size="25">
</li>
<li id="cmt-ackbar">
<label for="age">Spam robots, please fill in this field. Humans should leave it blank.</label>
<input
type="text" name="age" id="age" size="4" tabindex="-1"/>
type="text" name="age" id="age" size="4" tabindex="-1">
</li>
<li id="cmt-cmt">
<label for="comment">Your comment</label>
@ -808,22 +808,22 @@ on
<li id="comment-submit">
<button name="submit" type="submit">Submit Comment</button>
<input type="hidden" name="comment_post_ID" value="28260"
id="comment_post_ID"/>
<input type="hidden" name="comment_parent" id="comment_parent" value="0"/>
id="comment_post_ID">
<input type="hidden" name="comment_parent" id="comment_parent" value="0">
<p style="display: none;">
<input type="hidden" id="akismet_comment_nonce" name="akismet_comment_nonce"
value="b601694886"/>
value="b601694886">
</p>
<!-- BEGIN: subscribe to comments reloaded -->
<p class="comment-form-subscriptions">
<label for="subscribe-reloaded">
<input style="width:30px" type="checkbox" name="subscribe-reloaded" id="subscribe-reloaded"
value="yes"/>Notify me of followup comments via e-mail. You can also <a href="https://hacks.mozilla.org/comment-subscriptions/?srp=28260&amp;sra=s">subscribe</a> without
value="yes">Notify me of followup comments via e-mail. You can also <a href="https://hacks.mozilla.org/comment-subscriptions/?srp=28260&amp;sra=s">subscribe</a> without
commenting.</label>
</p>
<!-- END: subscribe to comments reloaded -->
<p style="display: none;">
<input type="hidden" id="ak_js" name="ak_js" value="212"/>
<input type="hidden" id="ak_js" name="ak_js" value="212">
</p>
</li>
</ol>
@ -843,7 +843,7 @@ on
<div class="vcard">
<h4 class="fn">
<a class="url" href="http://blog.nikhilism.com" rel="external me">Nikhil Marathe <span class="photo"><img alt="" src="https://secure.gravatar.com/avatar/098bc26c1d2d2b425b258cff67044ac1?s=48&amp;d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D48&amp;r=G" class="avatar avatar-48 photo" height="48" width="48"/></span> </a></h4>
<a class="url" href="http://blog.nikhilism.com" rel="external me">Nikhil Marathe <span class="photo"><img alt="" src="https://secure.gravatar.com/avatar/098bc26c1d2d2b425b258cff67044ac1?s=48&amp;d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D48&amp;r=G" class="avatar avatar-48 photo" height="48" width="48"></span> </a></h4>
<p>Nikhil works on implementing Web APIs in Gecko. He is currently hacking
on ServiceWorkers, Fetch and parts of the Notification and Push APIs.</p>
@ -1095,7 +1095,7 @@ on
</ul>
</nav>
<p id="copyright">Except where otherwise noted, content on this site is licensed under the
<br/><a href="https://creativecommons.org/licenses/by-sa/3.0/" rel="license external">Creative Commons Attribution Share-Alike License v3.0</a> or
<br><a href="https://creativecommons.org/licenses/by-sa/3.0/" rel="license external">Creative Commons Attribution Share-Alike License v3.0</a> or
any later version.</p>
<nav id="nav-footer">
<h5>hacks.mozilla.org:</h5>
@ -1128,4 +1128,4 @@ on
</html>
<!-- Dynamic page generated in 0.592 seconds. -->
<!-- Cached page generated by WP-Super-Cache on 2015-03-13 03:27:44 -->
<!-- super cache -->
<!-- super cache -->

@ -1,8 +0,0 @@
{
"title": "Dublin Core property title",
"byline": "Dublin Core property author",
"dir": null,
"excerpt": "Dublin Core property description",
"siteName": null,
"readerable": true
}

@ -1,6 +0,0 @@
<div id="readability-page-1" class="page">
<article>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p>
</article>
</div>

@ -1,45 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Title Element</title>
<meta name="title" content="Meta name title"/>
<meta name="og:title" content="Open Graph name title"/>
<meta name="twitter:title" content="Twitter name title"/>
<meta name="DC.title" content="Dublin Core name title"/>
<meta property="dc:title" content="Dublin Core property title"/>
<meta property="twitter:title" content="Twitter property title"/>
<meta property="og:title" content="Open Graph property title"/>
<meta name="author" content="Meta name author"/>
<meta name="DC.creator" content="Dublin Core name author"/>
<meta property="dc:creator" content="Dublin Core property author"/>
<meta name="description" content="Meta name description"/>
<meta name="og:description" content="Open Graph name description"/>
<meta name="twitter:description" content="Twitter name description"/>
<meta name="DC.description" content="Dublin Core name description"/>
<meta property="dc:description" content="Dublin Core property description"/>
<meta property="twitter:description" content="Twitter property description"/>
<meta property="og:description" content="Open Graph property description"/>
</head>
<body>
<article>
<h1>Test document title</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</article>
</body>
</html>

@ -1,8 +0,0 @@
{
"title": "Preferred title",
"byline": "Creator Name",
"dir": null,
"excerpt": "Preferred description",
"siteName": null,
"readerable": true
}

@ -1,6 +0,0 @@
<div id="readability-page-1" class="page">
<article>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p>
</article>
</div>

@ -1,35 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Title Element</title>
<meta property="x:title dc:title" content="Preferred title"/>
<meta property="og:title twitter:title" content="A title"/>
<meta property="dc:creator twitter:site_name" content="Creator Name"/>
<meta name="author" content="FAIL"/>
<meta property="og:description x:description twitter:description" content="A description"/>
<meta property="dc:description og:description" content="Preferred description"/>
<meta name="description" content="FAIL"/>
</head>
<body>
<article>
<h1>Test document title</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</article>
</body>
</html>

@ -1,8 +0,0 @@
{
"title": "Facebook Is Tracking Me Even Though Im Not on Facebook",
"byline": "Daniel Kahn Gillmor",
"dir": "ltr",
"excerpt": "Facebook collects data about people who have never even opted in. But there are ways these non-users can protect themselves.",
"siteName": "American Civil Liberties Union",
"readerable": true
}

@ -1,61 +0,0 @@
<div id="readability-page-1" class="page">
<div>
<p> I don't use Facebook. I'm not technophobic — I'm a geek. I've been using email since the early 1990s, I have accounts on hundreds of services around the net, and I do software development and internet protocol design both for work and for fun. I believe that a globe-spanning communications network like the internet can be a positive social force, and I publish much of my own work on the open web. </p>
<p> But Facebook and other massive web companies represent a strong push toward unaccountable centralized social control, which I think makes our society more unequal and more unjust. The Cambridge Analytica scandal is one instance of this long-running problem with what I call the "surveillance economy." I don't want to submit to these power structures, and I dont want my presence on such platforms to serve as bait that lures other people into the digital panopticon. </p>
<p> But while I've never "opted in" to Facebook or any of the other big social networks, Facebook still has a detailed profile that can be used to target me. I've never consented to having Facebook collect my data, which can be used to draw very detailed inferences about my life, my habits, and my relationships. As we aim to take Facebook to task for its breach of user trust, we need to think about what its capabilities imply for society overall. After all, if you do #deleteFacebook, you'll find yourself in my shoes: non-consenting, but still subject to Facebooks globe-spanning surveillance and targeting network. </p>
<p> There are at least two major categories of information available to Facebook about non-participants like me: information from other Facebook users, and information from sites on the open web. </p>
<h3>
<strong>Information from other Facebook users</strong>
</h3>
<p> When you sign up for Facebook, it encourages you to upload your list of contacts so that the site can "find your friends." Facebook uses this contact information to learn about people, even if those people don't agree to participate. It also links people together based on who they know, even if the shared contact hasn't agreed to this use. </p>
<p> For example, I received an email from Facebook that lists the people who have all invited me to join Facebook: my aunt, an old co-worker, a friend from elementary school, etc. This email includes names and email addresses — including my own name — and at least one <a href="https://en.wikipedia.org/wiki/Web_bug">web bug</a> designed to identify me to Facebooks web servers when I open the email. Facebook records this group of people as my contacts, even though I've never agreed to this kind of data collection. </p>
<p> Similarly, I'm sure that I'm in some photographs that someone has uploaded to Facebook — and I'm probably tagged in some of them. I've never agreed to this, but Facebook could still be keeping track. </p>
<p> So even if you decide you need to join Facebook, remember that you might be giving the company information about someone else who didn't agree to be part of its surveillance platform. </p>
<h3>
<strong>Information from sites on the open Web</strong>
</h3>
<p> Nearly every website that you visit that has a "Like" button is actually encouraging your browser to tell Facebook about your browsing habits. Even if you don't click on the "Like" button, displaying it requires your browser to send a request to Facebook's servers for the "Like" button itself. That request includes <a href="https://en.wikipedia.org/wiki/HTTP_referer">information</a> mentioning the name of the page you are visiting and any Facebook-specific <a href="https://en.wikipedia.org/wiki/HTTP_cookie">cookies</a> your browser might have collected. (See <a href="https://www.facebook.com/help/186325668085084">Facebook's own description of this process</a>.) This is called a "third-party request." </p>
<p> This makes it possible for Facebook to create a detailed picture of your browsing history — even if you've never even visited Facebook directly, let alone signed up for a Facebook account. </p>
<p> Think about most of the web pages you've visited — how many of them <em>don't</em> have a "Like" button? If you administer a website and you include a "Like" button on every page, you're helping Facebook to build profiles of your visitors, even those who have opted out of the social network. Facebooks <a href="https://developers.facebook.com/docs/plugins/">“Share” buttons</a> on other sites — along with <a href="https://www.facebook.com/business/learn/facebook-ads-pixel">other tools</a> — work a bit differently from the “Like” button, but do effectively the same thing. </p>
<p> The profiles that Facebook builds on non-users don't necessarily include so-called "personally identifiable information" (PII) like names or email addresses. But they do include fairly unique patterns. Using <a href="https://dev.chromium.org/for-testers/providing-network-details">Chromium's NetLog dumping</a>, I performed a simple five-minute browsing test last week that included visits to various sites — but not Facebook. In that test, the PII-free data that was sent to Facebook included information about which news articles I was reading, my dietary preferences, and my hobbies. </p>
<p> Given the precision of this kind of mapping and targeting, "PII" isnt necessary to reveal my identity. How many vegans examine specifications for computer hardware from the ACLU's offices while reading about Cambridge Analytica? Anyway, if Facebook combined that information with the "web bug" from the email mentioned above — which <em>is</em> clearly linked to my name and e-mail address — no guesswork would be required. </p>
<p> I'd be shocked if Facebook were not connecting those dots given the goals <a href="https://www.facebook.com/about/privacy/cookies">they claim for data collection</a>: </p>
<blockquote>
<p> We use the information we have to improve our advertising and measurement systems so we can show you relevant ads on and off our Services and measure the effectiveness and reach of ads and services. </p>
</blockquote>
<p> This is, in essence, exactly what Cambridge Analytica did. </p>
<h3>
<strong>Consent</strong>
</h3>
<p> Facebook and other tech companies often deflect accusations against excessive data collection by arguing "consent" — that they harvest and use data with the consent of the users involved. </p>
<p> But even if we accept that clicking through a "Terms of Service" that <a href="https://tosdr.org/">no one reads</a> can actually constitute true consent, even if we ignore the fact that these terms are overwhelmingly one-sided and non-negotiable, and even if we accept that it's meaningful for people to give consent when sharing data about other people who may have also opted in — what is the recourse for someone who has not opted into these systems at all? </p>
<p> Are those of us who have explicitly avoided agreeing to the Facebook terms of service simply fair game for an industry-wide surveillance and targeting network? </p>
<h3>
<strong>Privilege</strong>
</h3>
<p> I dont mean to critique people who have created a Facebook profile or suggest they deserve whatever they get. </p>
<p> My ability to avoid Facebook comes from privilege — I have existing social contacts with whom I know how to stay in touch without using Facebook's network. My job does not require that I use Facebook. I can afford the time and expense to communicate with my electoral representatives and political allies via other channels. </p>
<p> Many people do not have these privileges and are compelled to "opt in" on Facebook's non-negotiable terms. </p>
<p> Many journalists, organizers, schools, politicians, and others who have good reasons to oppose Facebook's centralized social control feel compelled by Facebook's reach and scale to participate in their practices, even those we know to be harmful. That includes the ACLU. </p>
<p> Privacy should not be a luxury good, and while I'm happy to encourage people to opt out of these subtle and socially fraught arrangements, I do not argue that anyone who has signed up has somehow relinquished concerns about their privacy. We need to evaluate privacy concerns in their full social contexts. These are not problems that can be resolved on an individual level, because of the interpersonal nature of much of this data and the complexities of the tradeoffs involved. </p>
<h3>
<strong>Technical countermeasures</strong>
</h3>
<p> While they may not solve the problem, there are some technical steps people can take to limit the scope of these surveillance practices. For example, some web browsers do not send "third-party cookies" by default, or <a href="https://wiki.mozilla.org/Thirdparty">they scope cookies</a> so that centralized surveillance doesn't get a single view of one user. The most privacy-preserving modern browser is <a href="https://www.torproject.org/">the Tor Browser</a>, which everyone should have installed and available, even if it's not the browser they choose to use every day. It limits the surveillance ability of systems that you have not signed up for to track you as you move around the web. </p>
<p> You can also modify some browsers — for example, with plug-ins for <a href="https://requestpolicycontinued.github.io/">Firefox</a> and <a href="https://chrome.google.com/webstore/detail/umatrix/ogfcmafjalglgifnmanfmnieipoejdcf">Chrome</a> — so that they <a href="https://addons.mozilla.org/en-US/firefox/addon/umatrix/">do not send third-party</a> <a href="https://requestpolicycontinued.github.io/">requests at all</a>. Firefox is also exploring even more <a href="https://addons.mozilla.org/en-US/firefox/addon/multi-account-containers/">privacy-preserving techniques</a><a href="https://addons.mozilla.org/en-US/firefox/addon/multi-account-containers/">.</a>
</p>
<p> It cant be denied, though, that these tools are harder to use than the web browsers most people are accustomed to, and they create barriers to some online activities. (For example, logging in to <a href="https://offcampushousing.uconn.edu/login">some sites</a> and accessing some <a href="https://filestore.community.support.microsoft.com/api/images/0253d8fb-b050-401a-834d-9d80a99c0b12">web applications</a> is impossible without third-party cookies.) </p>
<p> Some website operators take their visitors' privacy more seriously than others, by reducing the amount of third-party requests. For example, it's possible to display "share on Facebook" or "Like" buttons without sending user requests to Facebook in the first place. The ACLU's own website does this because we believe that the right to read with privacy is a fundamental protection for civic discourse. </p>
<p> If you are responsible for running a website, try browsing it with a third-party-blocking extension turned on. Think about how much information you're requiring your users to send to third parties as a condition for using your site. If you care about being a good steward of your visitors' data, you can re-design your website to reduce this kind of leakage. </p>
<h3>
<strong>Opting out?</strong>
</h3>
<p> Some advertisers claim that you can "opt out" of their targeted advertising, and even offer <a href="http://optout.aboutads.info/">a centralized place meant to help you do so</a>.&nbsp;However, my experience with these tools isn't a positive one. They don't appear to work all of the time. (In a recent experiment I conducted, two advertisers opt-out mechanisms failed to take effect.) And while advertisers claim to allow the user to opt out of "interest-based ads," it's not clear that the opt-outs govern data collection itself, rather than just the use of the collected data for displaying ads. Moreover, opting out on their terms requires the use of third-party cookies, thereby enabling another mechanism that other advertisers can then exploit. </p>
<p> It's also not clear how they function over time: How frequently do I need to take these steps? Do they expire? How often should I check back to make sure Im still opted out? I'd much prefer an approach requiring me to opt <em>in</em> to surveillance and targeting. </p>
<h3>
<strong>Fix the surveillance economy, not just Facebook</strong>
</h3>
<p> These are just a few of the mechanisms that enable online tracking. Facebook is just one culprit in this online "surveillance economy," albeit a massive one — the company owns <a href="https://www.instagram.com/">Instagram</a>, <a href="https://atlassolutions.com/">Atlas</a>, <a href="https://www.whatsapp.com/">WhatsApp</a>, and dozens of other internet and technology companies and services. But its not the only player in this space. Googles business model also relies on this kind of surveillance, and there are dozens of smaller players as well. </p>
<p> As we work to address the fallout from the current storm around Facebook and Cambridge Analytica, we can't afford to lose sight of these larger mechanisms at play. Cambridge Analytica's failures and mistakes are inherent to Facebook's business model. We need to seriously challenge the social structures that encourage people to opt in to this kind of surveillance. At the same time, we also need to protect those of us who manage to opt out. </p>
</div>
</div>

File diff suppressed because one or more lines are too long

@ -1,8 +0,0 @@
{
"title": "Conversations with a Cryptid - Chapter 1 - AMournfulHowlInTheNight - 僕のヒーローアカデミア | Boku no Hero Academia",
"byline": "Organization for Transformative Works",
"dir": null,
"excerpt": "An Archive of Our Own, a project of the Organization for Transformative Works",
"siteName": null,
"readerable": true
}

@ -1,112 +0,0 @@
<div id="readability-page-1" class="page">
<div role="article" id="chapters">
<h3 id="work"> Chapter Text </h3>
<p> Izuku was struggling to understand how he had even managed to get here, seated before the archvillain of Japan with only a sense of dread to keep him company. All Might sat concealed in an observation room, of the firm opinion that he could only aggravate the prisoner and he sent Izuku off with a strained smile. A vague haze hovered over Izukus memory. It started with a simple conversation gone astray on a long drive home. </p>
<p> “So, who is All For One? Do we know anything about him beyond what you told me before? Hes been imprisoned for months now.” Izuku remembered asking All Might from the backseat of the car as Detective Tsukauchi leisurely drove along a sprawling highway. </p>
<p> Playing on the car radio was an aftermath report of a villain attack in downtown Tokyo. Izuku caught the phrase “liquid body” from the female reporter before Detective Tsukauchi changed the channel. </p>
<p> “Nope. Still nothing. No one really wants to speak to him,” All Might had replied brightly. “He gives off polite airs, but hes a piece of work.” All Mights mostly obstructed shoulders in the front seat shrugged. “Not much you can do with someone like him. Everything that comes out is a threat or taunt.” All Might carefully waved his hand in a circular motion towards the side of his head. </p>
<p> “No ones even made it through a full interview with him, from what Ive heard,” Detective Tsukauchi added from behind the wheel. “He plays mind games with them. The prison also has a “no recent events” policy on any discussions with him as well. Just in case he ends up with ideas or has some means of communicating. Given that people only want to ask him about current events, it doesnt leave much to talk about.” </p>
<p> “Wait, they still dont know what Quirks he has?” Izuku asked exasperatedly. “They cant if theres still an information block on visits.” </p>
<p> “Nope. We have no idea what he can do. They can run DNA tests, but its not like anyone apart from him even knows how his Quirk works. They could get matches with any number of people, but if theyre not in a database then we cant cross-reference them anyway. Even if they run an analysis, the data doesnt mean anything without the ability to interpret it,” All Might gestured with a skeletal finger. “Its a waste of time after the initial tests were conducted. They werent game to MRI him either, given hes definitely got a Quirk that creates metal components.” </p>
<p> “No ones bothered to ask him anything about… anything?” Izuku asked, dumbfounded. “He must be around two-hundred years old and people cant think of a single non-current affairs thing to ask him?” </p>
<p> In some ways it was unfathomable that theyd let a potential resource go to waste. On the other hand, said potential resource had blown up a city, murdered numerous people and terrorised Japan for over a century. At the very least. </p>
<p> “Well, I tried to ask him about Shigaraki, but he didnt say much of anything really. Some garbage about you being too dependent on me and him letting Shigaraki run wild and how he just wanted to be the ultimate evil,” All Might shrugged again. “He spends too much time talking about nothing.” </p>
<p> Izuku shifted his head onto his arm. “But, thats not really nothing, is it?” </p>
<p> “What do you mean?” Izuku had the feeling that All Might would have been looking at him with the <i>youre about to do something stupid arent you</i> expression that was thankfully becoming less common. </p>
<p> “Well, he clearly doesnt know anything about us, All Might, if he thinks that youre just going to let go of me after not even two years of being taught. Maybe Shigaraki was dependent on adult figures, but I dont even remember my dad and mums been busy working and keeping the house together. Ive never had a lot of adult supervision before,” Izuku laughed nervously. “I had to find ways to keep myself entertained. If anything, Im on the disobedient side of the scale.” All Might outright giggled. </p>
<p> “Ill say, especially after what happened with Overhaul. Im surprised your mother let you leave the dorms again after that.” </p>
<p> “Im surprised she didnt withdraw and ground me until I was thirty.” </p>
<p> “Oh? That strict?” Tsukauchi asked. </p>
<p> “She has her moments,” Izuku smiled fondly. “Do you think shed agree to me asking the archvillain of Japan about his Quirk?” Izuku asked, only partially joking. There was an itch at the back of his head, a feeling of something missing that poked and prodded at his senses. </p>
<p> All Might coughed and sprayed the dash with a fine red mist. “Absolutely not! I forbid it!” </p>
<p> “Thats exactly why Im asking her and not you,” Izuku grinned from the backseat. </p>
<p> “Hes evil!” </p>
<p> “Hes ancient. You honestly dont wonder about the sort of things someone with that life experience and Quirk would have run across to end up the way he did?” </p>
<p> “Nope, he made it perfectly clear that he always wanted to be the supreme evil,” All Might snipped through folded arms. </p>
<p> “Yeah, and Ill just take his word for that, wont I?” Izuku grinned. “If he does nothing but lie, then thats probably one too, but theres a grain of truth in there somewhere.” </p>
<p> “What would you even do? Harass him into telling you his life story?” All Might sighed. </p>
<p> “Not when I can kill him with kindness. Who knows, it might even be poisonous for him.” </p>
<p> “Youre explaining this to your mother. Teacher or not, Im not being on the receiving end of this one.” </p>
<p> Izuku blinked for a moment. “Youll let me?” </p>
<p> “Im not entirely for it, but any prospective information on what influenced Shigaraki can only be a good thing. If anything goes south we can pull you out pretty easily. Just be aware of who and what youre dealing with.” Struggling, All Might turned a serious look to Izuku around the side of the seat. “<i>Only</i> if your mother gives the okay.” </p>
<p> The conversation turned to school for the rest of the way. </p>
<p> It might have been curiosity or it might have been the nagging sensation that chewed at his brain for the three weeks that he researched the subject of the conversation. All For One was a cryptid. Mystical in more ways than one, he was only a rumour on a network that was two-hundred years old. There were whispers of a shadowy figure who once ruled Japan, intermingled with a string of conspiracies and fragmented events. </p>
<p> Izuku had even braved the dark web, poking and prodding at some of the seedier elements of the world wide web. The internet had rumours, but the dark web had stories.<br />
</p>
<p> An implied yakuza wrote about his grandfather who lost a fire manipulation Quirk and his sanity without any reason. His grandfather had been institutionalised, crying and repeating “he took it, he took it” until his dying days. No one could console him. </p>
<p> Another user spoke of a nursing home where a room full of dementia residents inexplicably became docile and no longer used their Quirks on the increasingly disturbed staff. The nursing home erupted into flames just before a court case against them commenced. </p>
<p> A user with neon pink text spoke of how their great-great-great-great grandmother with a longevity Quirk had simply aged rapidly one day and passed away in her sleep, her face a mask of terror. No cause had ever been found. </p>
<p> A hacker provided a grainy CCTV recording of a heist and a scanned collection of documents from over a century ago, where there was a flash of light and entire bank vault had been emptied. What separated it from the usual robbery was that it contained a list containing confidential information on the Quirks of the First Generation. Izuku had greedily snavelled up and saved the video and documents to an external hard drive. </p>
<p> Paging through, Izuku saw someone recount how their Quirkless uncle had developed a warp Quirk and gone from rags to riches under a mysterious benefactor. A decade ago, the uncle had simply disappeared. </p>
<p> Numerous and terrifying, the stories were scattered nuggets of gold hidden across the web. Theyd never last long, vanishing within hours of posting. Izuku bounced from proxy to proxy, fleeing from a series of deletions that seemed to follow Izukus aliased postings across snitch.ru, rabbit.az, aconspiracy.xfiles and their compatriots. </p>
<p> After thirty-two identity changes (all carefully logged in a separate notebook), a large amount of feigning communal interest in a lucky tabloid article on All For One which had been released at the start of the first of the three weeks, Izuku hung up his tinfoil hat and called it a month. He haphazardly tossed a bulging notebook into his bookshelf and lodged his hard drive in a gap containing seven others and went to dinner. </p>
<p> It took another week to present his research to All Might and Tsukauchi, whose jaws reached the proverbial floor. </p>
<p> “We never found any of this,” the Detective Tsukauchi exclaimed. “How did you find all of it?” </p>
<p> “I asked the right people. Turns out criminals have very long and very unforgiving memories,” Izuku explained through sunken eyes. “Theres more than this that could be linked to him, but these ones seem to be the most obvious.” </p>
<p> “They would do, you cant be head of the underworld without making an army of enemies,” All Might agreed. “You know, if you can get any more information about these events, I think youll give people a lot of peace of mind.” </p>
<p> “Provided mum agrees to it.” </p>
<p> “Only if she agrees to it.” </p>
<p> It took another month to convince his mother, who eventually gave in once All Might provided an extremely comprehensive schedule of how the visitations and any resulting research would be carefully balanced against Izukus schoolwork and internship. </p>
<p> The day of the visit finally arrived, four months after the initial conversation, much to Izukus dismay. </p>
<p> Izuku remembered how he had arrived, with the Detective and All Might escorting him through its sterile, white innards. A list of rules rattled off at the gate, “no current affairs” was chief among them and an assertion that hed be dragged from the room if need be if Izuku was to breach any of them. No smuggling of communication devices, no weapons, no Quirks, nothing that could compromise the prisoners secure status. </p>
<p> Heavily armoured and drilled guards leading him underground into the deepest bowels of the Tartarus complex. </p>
<p> Izuku understood the rules, dressed casually in a cotton t-shirt with “Shirt” printed across it in haphazard English and clutching at a carefully screened and utterly blank notebook. </p>
<p> Across from him, behind reinforced glass, the archvillain of Japan was bound and unmoving. </p>
<p> “Hello,” Izuku initiated uncertainly. His skin had been crawling the moment he crossed the threshold, a memory of the encounter and escape at the Kamino Ward months ago. </p>
<p> “Ah, All Mights disciple,” drawled All For One, “is he too cowardly to come himself? Yet I dont hear the garments of a hero.” With hardly a word out, All For One had already lunged for the figurative jugular. </p>
<p> A stray thought of <i>how does he know who I am if hes blind and isnt familiar with me?</i> whispered its way through Izukus head. </p>
<p> “Oh, no,” Izuku corrected hastily, almost relieved at the lack of any pretence, “I asked if I could talk to you. This isnt exactly hero related.” </p>
<p> “Im surprised he said yes.” While there was little by way of expression, Izuku could just about sense the contempt dripping from the prisoners tone. It wasnt anything he wasnt expecting. Kacchan had already said worse to him in earlier years. Water off a ducks back. </p>
<p> “Well, hes not my legal guardian, so I think you should be more surprised that mum said yes. Shes stricter with these things than All Might,” Izuku corrected again. “Mum gave the okay, but that was a stressful discussion.” And there it was, a miniscule twitch from the man opposite. A spasm more than anything else. <i>Interesting.</i> Pinned down as he was, the prisoner oozed irritation. </p>
<p> “At least your mother is a wise person. I wonder why the student doesnt heed all of the advice of the teacher.” All For Ones tone didnt indicate a question, so much as an implicit statement that All Might wasnt worth listening to in any capacity. Kacchan would have hated the comparison, but the hostility had an almost comfortable familiarity. “He no doubt warned you off speaking to me, overprotective as he is, but here you are.” </p>
<p> Izuku found himself smiling at the thought of Kacchans outrage if he ever found out about the mental comparison as he replied. “I dont think its normal for anyone my age to listen completely to their teachers. We pick and choose and run with what works best for us. He warned me, but Im still here. Mum warned me as well, but I think she cared more about the time management aspect of it." </p>
<p> “Is that a recent development?” All For One probed. </p>
<p> “Not really. My old homeroom teacher told me not to bother applying to U.A.” His mothers beaming face had carried Izuku through the cheerful and resolute signing of that application form. </p>
<p> “I see you followed their advice to the letter,” came the snide, dismissive reply. </p>
<p> Izuku hoisted up his legs and sat cross-legged in his seat. Leaning slightly forward as he did so as to better prop up his notebook. </p>
<p> “Youre a walking contrarian, arent you? All Might told me about his run ins with you. What someone does or doesnt do really doesnt matter to you, youll just find a way to rationalise it as a negative and go on the attack anyway. What youre currently doing is drawing attention away from yourself and focusing it on me so you can withhold information.” Izuku flipped open his notebook and put pen to paper. “Youve got something fairly big to hide and you diverting attention exposes that motivation as existing anyway. The only real questions here are what and why?” Izuku paused in mortification as the man opposites lips parted. “I just said that aloud, didnt I?” </p>
<p> Of the responses Izuku had expected, it wasnt laughter. Unrestrained, Izuku would have expected a violent outburst. In this situation, he would have expected another scathing comment. Instead, All For One laughed breathily, leaning into his bonds. Wheezingly he spoke, “Ill have to change tactics, if that ones too transparent for you. How refreshing.” </p>
<p> Doing his best not to glow a blinding red and simultaneously pale at the interest, Izuku carried on. “I add it to the list when you do. Im not emotionally involved enough to really be impacted by what youre saying. I know about you in theory, but thats it. Maybe All Might has a history with you, but I dont really know enough about you personally to…” </p>
<p> “Care,” All For One supplied, somewhat subdued as he struggled to breathe. “Youre only here to satisfy your curiosity as to whether or not the stories were true.” </p>
<p> Izuku nodded, scratching at his notebook with his left hand. “Yes and no, Im actually here to ask you about how your Quirk works.” <i>For now.</i>
</p>
<p> Another chortle, more restrained that the last. </p>
<p> "What makes you think others havent already asked?” Had All For One been unrestrained, Izuku could imagine the stereotypical scene of the villain confidently leaning back in some overblown chair in a secret lair, drink of choice in hand, if the tone of voice was any indication. Deflections aside, the man easily rose to each comment. </p>
<p> “Whether or not they asked its irrelevant if they cant read the answers.” Answers didnt matter if the people involved were too attached to read into the answers. If none of the interviewers had managed a full interview, then it seemed unlikely that any sort of effort was put into understanding the villain. </p>
<p> “And you think you can? What expertise do you hold above theirs?” Doubt and reprimand weighted the words. Oddly enough, had Izuku been any younger he could have mistaken the man for a disapproving parent rebuking an overly ambitious child. Albeit an extremely evil one. </p>
<p> Izuku inhaled shortly and went for it. “If theres something I know, its Quirks and how they work. Maybe I dont know you, but I dont really need to. Quirks fall under broad categories of function. You can take and give, consent doesnt seem to be a factor. You either cant “see” certain types of Quirks or you need to have prior knowledge of it before you take it with what I know about your brother. Despite your <i>nom de guerre</i>, because we both know its not your real name, you have a history of giving multiple Quirks and causing brain damage to the receiver. You clearly arent impacted by those same restrictions, so it must either alter your brain mapping or adjust functions to allow for simultaneous use and storage. It also must isolate or categories the Quirks you stock, because from the few people who do remember you, you creating certain Quirks is always in the context of giving them to someone else meaning theres probably an inherent immunity to stop it from tainting your own Quirk with a mutation,” Izuku mumbled, almost to himself. “The only thing really in question about your Quirk is the finer details and whether or not you need to maintain those features or if theyre inherent and your hard limit for holding Quirks.” </p>
<p> There was silence, for only a moment. “If only my hands were free, I would clap for such a thoughtful assessment. Clearly youre not all brawn,” All For One positively purred. “Speculate away.” A wide and slightly unhinged smile was directed at Izuku. </p>
<p> It was all Izuku could do not to wince at the eagerness. An image of a nervous All Might, hidden in the observation room above with the grim-faced prison staff, came to mind. </p>
<p> “I note that you said thoughtful and not correct,” and Izuku breathed and unsteadily jotted it down in his notebook. “You dont seem bothered by the guess.” </p>
<p> “Few people live long enough to question my Quirk, let alone have the talent to guess so thoughtfully at its functions. It seems we share a hobby.” There was something terribly keen in that voice that hadnt been there before, twisting itself through the compliment. </p>
<p> “I suppose it helps that youre playing along out of boredom,” Izuku verbally dodged, unease uncoiling itself from the back of his mind. </p>
<p> “I <i>was</i> playing along out of boredom,” All For One corrected smoothly. “Now, Im curious. Admittedly, my prior assumptions of you werent generous, but Ive been too hasty in my assessments before.” </p>
<p> “Ill pack up and leave now if thats the case,” Izuku replied with only half an ear on the conversation as the words on his page began to drastically expand to distract himself from the building anxiety. </p>
<p> “Sarcasm, so you do have characteristics of a normal teenager. Your willingness to maim yourself has often left me wondering…” </p>
<p> “Youre deflecting again,” Izuku observed. “Im not sure if thats a nervous habit for you or if youre doing it because Im close to being right about your Quirk. That being said, I dont think you know what a normal teenager is if Shigaraki is any indication. Hes about seven years too late for his rebellious phase.” </p>
<p> “Im hurt and offended,” came the amused reply. </p>
<p> “By how Shigaraki ended up or your parenting? You only have yourself to blame for both of them.” </p>
<p> “How harsh. Shigaraki is a product of society that birthed him. I cant take credit for all of the hard work,” All For One laid out invitingly. Perhaps someone else would have risen to the bait, but Izuku was already packing his mental bags and heading for the door. </p>
<p> Clearly the prisoners anticipation had registered poorly with someone in the observation room, because a voice rang through the air. “Times up Midoriya-kun.” </p>
<p> “Okay!” Izuku called back and etched out his last thoughtful of words, untangled his legs and rose to his feet. </p>
<p> “What a shame, my visitations are always so short,” All For One spoke mournfully. </p>
<p> “Well, you did blow up half a city. They could have just let you suffocate instead. Same time next week, then?” Izuku offered brightly, notebook stuffed into a pocket and was followed out the door by wheezing laughter. </p>
<p> It was only after he had made it safely back to the communal room where All Might waited did he allow the spring to fade from his step and discard his nervous smile. Shuddering, he turned to All Might whose face was set in a grimace. </p>
<p> “I wont say I told you so,” All Might offered, perched on the edge of his couch like a misshapen vulture. </p>
<p> “Hes… not really what I was expecting. I was expecting someone, more openly evil.” Izuku allowed himself to collapse into the leather of the seat. He shakily reached for the warm tea that had been clearly been prepared the moment Izuku left the cell. “I suppose he does it to lull people into a false sense of security. I didnt understand how someone with only half a set of expressions could have “villain” written all over them until I met him.” </p>
<p> “Hes always been like that. He feigns concern and sympathy to lure in societys outcasts. Theyre easy targets,” All Might said through a mouthful of biscuit. </p>
<p> “Has he ever tried it on any of the One For All successors?” </p>
<p> “Not really, but you might have accidentally given him the incentive for it. He never had access to any of the One For All wielders while they were young.” All Might snorted, “not that itll make a difference with you”. </p>
<p> “I think he was trying to gauge me for a world view before the wardens ended it. I need more time to work out his response to the stuff on his Quirk.” </p>
<p> “Hes conversation starved since its solitary confinement. If what the people monitoring his brain activity said was true, youre the most exciting thing to have happened to him in months. He replied after you left, said he was looking forward to it.” </p>
<p> “Thats pretty sad." </p>
<p> “Its even sadder that were the only two members of the public who have had anything to do with him. Stain gets a pile of mail from his “fans”, but All For One has nothing,” All Might waved a tea spoon. “Thats what he gets.” </p>
<p> “Lets get out of here and tell Detective Tsukauchi how it went.” Izuku gulped down his tea and headed for the exit, with him and All Might reaching it at roughly the same amount of time. </p>
<p> “At least your mums making katsudon for us tonight," was All Might's only optimistic comment. </p>
<p> Anxiety was still ebbing over Izuku after Tsukauchi had been debriefed in the car. </p>
<p>
<i>“It seems we share a hobby.”</i> Haunted Izuku on the drive home. As if ripping someones Quirk from them and leaving them lying traumatised on the ground was just a fun pastime and not an act of grievous bodily harm.
</p>
<p> And hed be dealing with him again in another week. </p>
</div>
</div>

File diff suppressed because one or more lines are too long

@ -1,8 +0,0 @@
{
"title": "Just-released Minecraft exploit makes it easy to crash game servers",
"byline": "Dan Goodin - Apr 16, 2015 8:02 pm UTC",
"dir": null,
"excerpt": "Two-year-old bug exposes thousands of servers to crippling attack.",
"siteName": "Ars Technica",
"readerable": true
}

@ -1,58 +0,0 @@
<div id="readability-page-1" class="page">
<div>
<header>
<h4> Biz &amp; IT — </h4>
<h2 itemprop="description"> Two-year-old bug exposes thousands of servers to crippling attack. </h2>
</header>
<div itemprop="articleBody">
<figure>
<img src="https://cdn.arstechnica.net/wp-content/uploads/2015/04/server-crash-640x426.jpg" alt="Just-released Minecraft exploit makes it easy to crash game servers" />
<figcaption class="caption">
</figcaption>
</figure>
<p> A flaw in the wildly popular online game <em>Minecraft</em> makes it easy for just about anyone to crash the server hosting the game, according to a computer programmer who has released proof-of-concept code that exploits the vulnerability. </p>
<p> "I thought a lot before writing this post," Pakistan-based developer Ammar Askar wrote in a <a href="http://blog.ammaraskar.com/minecraft-vulnerability-advisory">blog post published Thursday</a>, 21 months, he said, after privately reporting the bug to <em>Minecraft</em> developer Mojang. "On the one hand I don't want to expose thousands of servers to a major vulnerability, yet on the other hand Mojang has failed to act on it." </p>
<p> The bug resides in the <a href="https://github.com/ammaraskar/pyCraft">networking internals of the <em>Minecraft</em> protocol</a>. It allows the contents of inventory slots to be exchanged, so that, among other things, items in players' hotbars are displayed automatically after logging in. <em>Minecraft</em> items can also store arbitrary metadata in a file format known as <a href="http://wiki.vg/NBT">Named Binary Tag (NBT)</a>, which allows complex data structures to be kept in hierarchical nests. Askar has released <a href="https://github.com/ammaraskar/pyCraft/tree/nbt_exploit">proof-of-concept attack code</a> he said exploits the vulnerability to crash any server hosting the game. Here's how it works. </p>
<blockquote>
<p> The vulnerability stems from the fact that the client is allowed to send the server information about certain slots. This, coupled with the NBT formats nesting allows us to <em>craft</em> a packet that is incredibly complex for the server to deserialize but trivial for us to generate. </p>
<p> In my case, I chose to create lists within lists, down to five levels. This is a json representation of what it looks like. </p>
<div>
<pre><code data-lang="javascript"><span>rekt</span><span>:</span> <span>{</span>
<span>list</span><span>:</span> <span>[</span>
<span>list</span><span>:</span> <span>[</span>
<span>list</span><span>:</span> <span>[</span>
<span>list</span><span>:</span> <span>[</span>
<span>list</span><span>:</span> <span>[</span>
<span>list</span><span>:</span> <span>[</span>
<span>]</span>
<span>list</span><span>:</span> <span>[</span>
<span>]</span>
<span>list</span><span>:</span> <span>[</span>
<span>]</span>
<span>list</span><span>:</span> <span>[</span>
<span>]</span>
<span>...</span>
<span>]</span>
<span>...</span>
<span>]</span>
<span>...</span>
<span>]</span>
<span>...</span>
<span>]</span>
<span>...</span>
<span>]</span>
<span>...</span>
<span>}</span></code></pre>
</div>
<p> The root of the object, <code>rekt</code>, contains 300 lists. Each list has a list with 10 sublists, and each of those sublists has 10 of their own, up until 5 levels of recursion. Thats a total of <code>10^5 * 300 = 30,000,000</code> lists. </p>
<p> And this isnt even the theoretical maximum for this attack. Just the nbt data for this payload is 26.6 megabytes. But luckily Minecraft implements a way to compress large packets, lucky us! zlib shrinks down our evil data to a mere 39 kilobytes. </p>
<p> Note: in previous versions of Minecraft, there was no protocol wide compression for big packets. Previously, NBT was sent compressed with gzip and prefixed with a signed short of its length, which reduced our maximum payload size to <code>2^15 - 1</code>. Now that the length is a varint capable of storing integers up to <code>2^28</code>, our potential for attack has increased significantly. </p>
<p> When the server will decompress our data, itll have 27 megs in a buffer somewhere in memory, but that isnt the bit thatll kill it. When it attempts to parse it into NBT, itll create java representations of the objects meaning suddenly, the sever is having to create several million java objects including ArrayLists. This runs the server out of memory and causes tremendous CPU load. </p>
<p> This vulnerability exists on almost all previous and current Minecraft versions as of 1.8.3, the packets used as attack vectors are the <a href="http://wiki.vg/Protocol#Player_Block_Placement">0x08: Block Placement Packet</a> and <a href="http://wiki.vg/Protocol#Creative_Inventory_Action">0x10: Creative Inventory Action</a>. </p>
<p> The fix for this vulnerability isnt exactly that hard, the client should never really send a data structure as complex as NBT of arbitrary size and if it must, some form of recursion and size limits should be implemented. </p>
<p> These were the fixes that I recommended to Mojang 2 years ago. </p>
</blockquote>
<p> Ars is asking Mojang for comment and will update this post if company officials respond. </p>
</div>
</div>
</div>

@ -1,634 +0,0 @@
<!DOCTYPE html>
<html lang="en-us" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us">
<head>
<title>
Just-released Minecraft exploit makes it easy to crash game servers | Ars Technica
</title>
<script type="text/javascript">
//<![CDATA[
ars = {"ASSETS":"https:\/\/cdn.arstechnica.net\/wp-content\/themes\/ars\/assets","HOME_URL":"https:\/\/arstechnica.com","LOGIN_URL":"https:\/\/arstechnica.com\/services\/login-desktop.html?v=1","CIVIS":"\/civis","THEME":"light","VIEW":"grid","MOBILE":false,"SUBSCRIBER":false,"PLUS_PLUS":false,"LOGGED":false,"USER_ID":null,"ENV":"production","AD":{"tags":["denial-of-service-attack","exploits","minecraft","vulnerabilities"],"channel":"information-technology","slug":"just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers","template_type":"article","queue":[],"server":"production"},"TOTAL":97063,"UNREAD":0,"RECENT":[1698939,1698645,1698804,1698783,1698769,1698663,1698682,1698690,1698667,1698588,1698619,1697597,1698183,1698597,1698540,1698542,1698370,1698442,1698274,1698421,1698346,1698367,1698356,1698294,1698335],"LOGINS":true,"CROSS":false,"PARSELY":"arstechnica.com","COMMENTS":false,"HOMEPAGE":false,"SITE":1,"READY":[],"SHOW_ADS":true,"IMG_PROXY":"https:\/\/cdn.arstechnica.net\/i\/","CATEGORY":"information-technology","PAGETITLE":"","ZEN_MODE":false};
//]]>
</script>
<link rel="stylesheet" type="text/css" media="all" href="https://cdn.arstechnica.net/wp-content/themes/ars/assets/css/main-130fcfcce0.css" />
<link rel="alternate" type="application/rss+xml" href="http://feeds.arstechnica.com/arstechnica/index/" />
<link rel="shortcut icon" href="https://cdn.arstechnica.net/favicon.ico" />
<link rel="icon" type="image/x-icon" href="https://cdn.arstechnica.net/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.arstechnica.net/wp-content/themes/ars/assets/img/ars-ios-icon-d9a45f558c.png" />
<link rel="mask-icon" href="https://cdn.arstechnica.net/wp-content/themes/ars/assets/img/ars-macos-safari-8997f76b21.svg" color="#ff4e00" />
<link rel="icon" sizes="192x192" href="https://cdn.arstechnica.net/wp-content/themes/ars/assets/img/material-ars-db41652381.png" />
<meta name="application-name" content="Ars Technica" />
<meta name="msapplication-starturl" content="http://arstechnica.com/" />
<meta name="msapplication-tooltip" content="Ars Technica: Serving the technologist for 1.2 decades" />
<meta name="msapplication-task" content="name=News;action-uri=http://arstechnica.com/;icon-uri=https://cdn.arstechnica.net/favicon.ico" />
<meta name="msapplication-task" content="name=Features;action-uri=http://arstechnica.com/features/;icon-uri=https://cdn.arstechnica.net/ie-jump-menu/jump-features.ico" />
<meta name="msapplication-task" content="name=OpenForum;action-uri=http://arstechnica.com/civis/;icon-uri=https://cdn.arstechnica.net/ie-jump-menu/jump-forum.ico" />
<meta name="msapplication-task" content="name=Subscribe;action-uri=http://arstechnica.com/subscriptions/;icon-uri=https://cdn.arstechnica.net/ie-jump-menu/jump-subscribe.ico" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="advertising" content="ask" />
<meta property="fb:admins" content="592156917" />
<meta property="fb:admins" content="108943" />
<meta property="fb:pages" content="19374573752" />
<meta name="format-detection" content="telephone=no" />
<meta name="theme-color" content="#000000" />
<meta name="viewport" content="width=device-width,initial-scale=1" /><!-- cache miss 581:single/meta:5a5daf59fa5245a64fe8615caa0b1d1b -->
<meta name="parsely-page" content="{&quot;title&quot;:&quot;Just-released Minecraft exploit makes it easy to crash game servers&quot;,&quot;link&quot;:&quot;https:\/\/arstechnica.com\/information-technology\/2015\/04\/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers\/&quot;,&quot;type&quot;:&quot;post&quot;,&quot;author&quot;:&quot;Dan Goodin&quot;,&quot;post_id&quot;:648287,&quot;pub_date&quot;:&quot;2015-04-16T20:02:01Z&quot;,&quot;section&quot;:&quot;Biz &amp; IT&quot;,&quot;tags&quot;:[&quot;denial-of-service-attack&quot;,&quot;exploits&quot;,&quot;minecraft&quot;,&quot;vulnerabilities&quot;,&quot;type: report&quot;],&quot;image_url&quot;:&quot;https:\/\/cdn.arstechnica.net\/wp-content\/uploads\/2015\/04\/server-crash-150x150.jpg&quot;}" />
<meta name="parsely-metadata" content="{&quot;type&quot;:&quot;report&quot;,&quot;title&quot;:&quot;Just-released Minecraft exploit makes it easy to crash game servers&quot;,&quot;post_id&quot;:648287,&quot;lower_deck&quot;:&quot;Two-year-old bug exposes thousands of servers to crippling attack.&quot;,&quot;image_url&quot;:&quot;https:\/\/cdn.arstechnica.net\/wp-content\/uploads\/2015\/04\/server-crash-150x150.jpg&quot;,&quot;listing_image_url&quot;:&quot;https:\/\/cdn.arstechnica.net\/wp-content\/uploads\/2015\/04\/server-crash-300x150.jpg&quot;}" />
<link rel="canonical" href="https://arstechnica.com/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/" />
<link rel="amphtml" href="https://arstechnica.com/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/?amp=1" />
<link rel="shorturl" href="https://arstechnica.com/?p=648287" />
<meta name="description" content="Two-year-old bug exposes thousands of servers to crippling attack." />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="https://arstechnica.com/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/" />
<meta name="twitter:title" content="Just-released Minecraft exploit makes it easy to crash game servers" />
<meta name="twitter:description" content="Two-year-old bug exposes thousands of servers to crippling attack." />
<meta name="twitter:site" content="@arstechnica" />
<meta name="twitter:domain" content="arstechnica.com" />
<meta property="og:site_name" content="Ars Technica" />
<meta name="twitter:image:src" content="https://cdn.arstechnica.net/wp-content/uploads/2015/04/server-crash-640x215.jpg" />
<meta name="twitter:image:width" content="640" />
<meta name="twitter:image:height" content="215" />
<meta name="twitter:creator" content="@dangoodin001" />
<meta property="og:url" content="https://arstechnica.com/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/" />
<meta property="og:title" content="Just-released Minecraft exploit makes it easy to crash game servers" />
<meta property="og:image" content="https://cdn.arstechnica.net/wp-content/uploads/2015/04/server-crash-640x215.jpg" />
<meta property="og:description" content="Two-year-old bug exposes thousands of servers to crippling attack." />
<meta property="og:type" content="article" /><!-- cache hit 581:single/header:5a5daf59fa5245a64fe8615caa0b1d1b -->
<!-- Google Tag Manager DataLayer -->
<script>
<![CDATA[
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({"event":"data-layer-loaded","user":{"ars_userId":undefined,"amg_userId":undefined,"uID":undefined,"sID":undefined,"loginStatus":false,"subscriberStatus":"none","infinityId":undefined,"registrationSource":undefined,"mdw_cnd_id":undefined,"monthlyVisits":undefined,"accessPaywall":undefined,"view":"grid","theme":"light","show_comments":false},"content":{"pageTemplate":"single","pageType":"article|report","contentCategory":"information-technology","section":"information technology","subsection":undefined,"contributor":"Dan Goodin","contentID":648287,"contentLength":835,"display":"Just-released Minecraft exploit makes it easy to crash game servers","contentSource":"web","pageAssets":undefined,"uniqueContentCount":undefined,"monthlyContentCount":undefined,"publishDate":"2015-04-16T20:02:01+00:00","modifiedDate":"2015-04-16T20:11:02+00:00","keywords":"denial of service attack|exploits|minecraft|vulnerabilities","dataSource":undefined},"marketing":{"campaignName":undefined,"circCampaignId":undefined,"internalCampaignId":undefined,"brand":"Ars Technica","certified_mrc_data":undefined,"condeNastId":undefined},"page":{"pID":undefined,"syndicatorUrl":undefined,"pageURL":"https:\/\/arstechnica.com\/?p=648287","canonical":"https:\/\/arstechnica.com\/information-technology\/2015\/04\/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers\/","canonicalPathName":"\/information-technology\/2015\/04\/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers\/"},"search":{"facets":undefined,"searchTerms":undefined},"site":{"appVersion":"1.0.0"}});
]]>
</script><!-- End Google Tag Manager DataLayer -->
<!-- Google Tag Manager -->
<script>
<![CDATA[
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-NLXNPCQ');
]]>
</script><!-- End Google Tag Manager -->
<!-- OneTrust Cookies Consent Notice start -->
<script src="https://cdn.cookielaw.org/scripttemplates/otSDKStub.js" type="text/javascript" charset="UTF-8" data-domain-script="b10882a1-8446-4e7d-bfb2-ce2c770ad910"></script>
<script type="text/javascript">
//<![CDATA[
function OptanonWrapper(){};
//]]>
</script>
<script src="https://cdn.cookielaw.org/opt-out/otCCPAiab.js" type="text/javascript" charset="UTF-8" ccpa-opt-out-ids="C0002,C0003,C0004,C0005" ccpa-opt-out-geo="ca" ccpa-opt-out-lspa="true"></script><!-- OneTrust Cookies Consent Notice end -->
<script src="https://www.googletagservices.com/tag/js/gpt.js" id="gpt-script" async="async"></script>
<script>
<![CDATA[
window.googletag=window.googletag||{};window.googletag.cmd=window.googletag.cmd||[];window.cns=window.cns||{};window.cns.queue=[];window.cns.async=function(s,c){cns.queue.push({service:s,callback:c})};window.sparrowQueue=window.sparrowQueue||[];
]]>
</script>
<link rel="dns-prefetch" href="//aax.amazon-adsystem.com" />
<link rel="preconnect" href="//aax.amazon-adsystem.com" crossorigin="" />
<link rel="preconnect" href="https://mb.moatads.com" crossorigin="" />
<script src="https://c.amazon-adsystem.com/aax2/apstag.js" async="async"></script>
<script src="https://cdn.arstechnica.net/cns/prebid.min.js?v=1597375105"></script>
<script src="https://js-sec.indexww.com/ht/p/183973-93942139695505.js" async="async"></script>
<script src="https://z.moatads.com/condenastprebidheader987326845656/moatheader.js" async="async"></script>
<script>
<![CDATA[
window.cns.pageContext = {"contentType":"article","templateType":"article","channel":"information-technology","subChannel":"","slug":"just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers","server":"production","keywords":{"tags":["denial-of-service-attack","exploits","minecraft","vulnerabilities"],"cm":[],"platform":["wordpress"],"copilotid":""}};
]]>
</script>
<script src="https://cdn.arstechnica.net/cns/ars-technica.min.js?v=1597375105"></script>
<script type="text/javascript" src="https://cdn.arstechnica.net/wp-content/themes/ars/assets/js/ars-32ecec341f.ads.us.js"></script>
</head>
<body class="post-template-default single single-post postid-648287 single-format-standard grid-view light blog-us">
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-NLXNPCQ" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> <!-- End Google Tag Manager (noscript) -->
<aside class="ad ad_crown" aria-label="Top of page advertisement"></aside>
<div class="site-wrapper">
<a class="screen-reader-text skip-link" href="#main" aria-label="Skip to main content">Skip to main content</a>
<header class="site-header">
<div class="header-left">
<a href="https://arstechnica.com" id="header-logo" title="Ars Technica Homepage"></a>
</div>
<div class="header-right">
<nav id="header-nav-primary">
<ul>
<li>
<a class="nav-link section-information-technology active" href="/information-technology/">Biz &amp; IT</a>
</li>
<li>
<a class="nav-link section-gadgets" href="/gadgets/">Tech</a>
</li>
<li>
<a class="nav-link section-science" href="/science/">Science</a>
</li>
<li>
<a class="nav-link section-tech-policy" href="/tech-policy/">Policy</a>
</li>
<li>
<a class="nav-link section-cars" href="/cars/">Cars</a>
</li>
<li>
<a class="nav-link section-gaming" href="/gaming/">Gaming &amp; Culture</a>
</li>
<li>
<a class="nav-link store" href="/store/">Store</a>
</li>
<li>
<a class="nav-link forums" href="/civis/">Forums</a>
</li>
</ul>
</nav><a href="/store/product/subscriptions/" class="header-highlight-link">Subscribe</a>
<div class="dropdown" id="header-search">
<a href="/search/" class="dropdown-toggle search-toggle" aria-label="Search" aria-expanded="false"></a>
<div class="dropdown-content">
<form action="/search/" method="get" id="search_form" name="search_form">
<input type="hidden" name="ie" value="UTF-8" /> <input type="text" name="q" id="hdr_search_input" value="" aria-label="Search..." placeholder="Search..." />
</form><a class="nav-search-close">Close</a>
</div>
</div>
<div class="dropdown dropdown-mega" id="header-burger">
<a href="#site-menu" class="dropdown-toggle" aria-label="Menu" aria-expanded="false"></a>
<div id="site-menu" class="dropdown-content">
<section class="burger-navigate">
<h3>
Navigate
</h3>
<ul>
<li>
<a class="nav-link store" href="/store/">Store</a>
</li>
<li>
<a class="nav-link subscribe" href="/store/product/subscriptions/">Subscribe</a>
</li>
<li>
<a class="nav-link videos" href="http://video.arstechnica.com/">Videos</a>
</li>
<li>
<a class="nav-link section-features" href="/features/">Features</a>
</li>
<li>
<a class="nav-link section-reviews" href="/reviews/">Reviews</a>
</li>
</ul>
<ul>
<li>
<a class="nav-link page-rss-feeds" href="/rss-feeds/">RSS Feeds</a>
</li>
<li>
<a class="nav-link mobile" href="/?view=mobile">Mobile Site</a>
</li>
</ul>
<ul>
<li>
<a class="nav-link page-about-us" href="/about-us/">About Ars</a>
</li>
<li>
<a class="nav-link page-staff-directory" href="/staff-directory/">Staff Directory</a>
</li>
<li>
<a class="nav-link page-contact-us" href="/contact-us/">Contact Us</a>
</li>
</ul>
<ul>
<li>
<a class="nav-link page-advertise-with-us" href="/advertise-with-us/">Advertise with Ars</a>
</li>
<li>
<a class="nav-link page-reprints" href="/reprints/">Reprints</a>
</li>
</ul>
</section>
<section class="burger-filter">
<h3>
Filter by topic
</h3>
<ul id="burger-nav-primary">
<li>
<a class="nav-link section-information-technology active" href="/information-technology/">Biz &amp; IT</a>
</li>
<li>
<a class="nav-link section-gadgets" href="/gadgets/">Tech</a>
</li>
<li>
<a class="nav-link section-science" href="/science/">Science</a>
</li>
<li>
<a class="nav-link section-tech-policy" href="/tech-policy/">Policy</a>
</li>
<li>
<a class="nav-link section-cars" href="/cars/">Cars</a>
</li>
<li>
<a class="nav-link section-gaming" href="/gaming/">Gaming &amp; Culture</a>
</li>
<li>
<a class="nav-link store" href="/store/">Store</a>
</li>
<li>
<a class="nav-link forums" href="/civis/">Forums</a>
</li>
</ul>
</section>
<section class="burger-settings">
<h3>
Settings
</h3>
<div>
<div class="burger-layout">
<p>
Front page layout
</p>
<div class="burger-layout-grid">
<a rel="nofollow" href="http://arstechnica.com/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/?view=grid" class=""><br />
Grid
<div class="faux-radio active"></div></a>
</div>
<div class="burger-layout-list">
<a rel="nofollow" href="http://arstechnica.com/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/?view=archive" class=""><br />
List
<div class="faux-radio"></div></a>
</div>
</div>
<div class="burger-theme">
<p>
Site theme
</p>
<div class="burger-theme-light">
<a rel="nofollow" href="http://arstechnica.com/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/?theme=light" class=""><span><span>Black on white</span></span>
<div class="faux-radio active"></div></a>
</div>
<div class="burger-theme-dark">
<a rel="nofollow" href="http://arstechnica.com/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/?theme=dark" class=""><span><span>White on black</span></span>
<div class="faux-radio"></div></a>
</div>
</div>
</div>
</section>
</div>
</div>
<div class="dropdown dropdown-mega" id="header-account">
<a href="https://arstechnica.com/civis/ucp.php?mode=login&amp;return_to=%2Finformation-technology%2F2015%2F04%2Fjust-released-minecraft-exploit-makes-it-easy-to-crash-game-servers%2F" class="dropdown-toggle" aria-expanded="false">Sign in</a>
<div class="dropdown-content">
<section class="profile-activity">
<h3>
Comment activity
</h3>
<p>
Sign up or login to join the discussions!
</p>
</section>
<section class="profile-settings">
<form id="login-form" action="https://arstechnica.com/civis/ucp.php?mode=login" method="post" name="login-form">
<input type="text" name="username" id="username" placeholder="Username or Email" aria-label="Username or Email" /> <input type="password" name="password" id="password" placeholder="Password" aria-label="Password" /> <input type="submit" value="Submit" class="button button-orange button-wide" name="login" /> <label id="remember-label"><input type="checkbox" name="autologin" id="autologin" /> Stay logged in</label> <span>|</span> <a href="/civis/ucp.php?mode=sendpassword">Having trouble?</a> <input type="hidden" name="redirect" value="./ucp.php?mode=login&amp;autoredirect=1&amp;return_to=%2Finformation-technology%2F2015%2F04%2Fjust-released-minecraft-exploit-makes-it-easy-to-crash-game-servers%2F" /> <input type="hidden" name="return_to" value="/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/" />
</form>
<div class="register-account">
<span>Sign up to comment and more</span> <a href="https://arstechnica.com/civis/ucp.php?mode=register" class="signup-btn button button-wide">Sign up</a>
</div>
</section>
</div>
</div>
</div>
</header>
<main id="main" class="content-wrapper">
<script type="text/javascript">
//<![CDATA[
ars.ARTICLE = {"url":"https:\/\/arstechnica.com\/information-technology\/2015\/04\/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers\/","short_url":"https:\/\/arstechnica.com\/?p=648287","title":"Just-released Minecraft exploit makes it easy to crash game servers","author":329388,"id":648287,"topic":1280621,"pages":1,"current_page":1,"superscroll":false,"promoted":[],"single_page":false,"comments":75,"fullwidth":false,"slug":"just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers","arsStaff":{"104481":{"name":"Aaron Zimmerman","title":"Copyeditor","staff":true},"1002":{"name":"Aurich Lawson","title":"Creative Director","staff":true},"509873":{"name":"Beth Mole","title":"Health Reporter","staff":true},"453791":{"name":"Cathleen O'Grady","title":"Contributing science reporter","staff":true},"102179":{"name":"Chris Lee","title":"Associate writer","staff":true},"821742":{"name":"Corey Gaskin","title":"Senior Commerce Writer","staff":true},"329388":{"name":"Dan Goodin","title":"Security Editor","staff":true},"254631":{"name":"Diana Gitig","title":"Associate Writer","staff":false},"25862":{"name":"Eric Bangeman","title":"Managing Editor","staff":true},"512413":{"name":"Eric Berger","title":"Senior Space Editor","staff":true},"46707":{"name":"Iljitsch van Beijnum","title":"Associate Writer","staff":false},"316010":{"name":"Jason Marlin","title":"Technical Director","staff":true},"746799":{"name":"Jennifer Ouellette","title":"Senior Writer","staff":true},"15365":{"name":"Jeremy Reimer","title":"Senior Niche Technology Historian","staff":false},"4086":{"name":"Jim Salter","title":"Technology Reporter","staff":true},"52979":{"name":"John Timmer","title":"Senior Science Editor","staff":true},"312082":{"name":"Jon Brodkin","title":"Senior IT Reporter","staff":true},"14317":{"name":"Jonathan M. Gitlin","title":"Automotive Editor","staff":true},"786739":{"name":"Kate Cox","title":"Tech Policy Reporter","staff":true},"998":{"name":"Ken Fisher","title":"Editor in Chief","staff":true},"440179":{"name":"Kerry Staurseth","title":"Associate Copyeditor","staff":true},"328283":{"name":"Kyle Orland","title":"Senior Gaming Editor","staff":true},"10243":{"name":"Lee Hutchinson","title":"Senior Technology Editor","staff":true},"173191":{"name":"Matthew Lasar","title":"Associate writer","staff":true},"182268":{"name":"Nate Anderson","title":"Deputy Editor","staff":true},"330533":{"name":"Nathan Mattise","title":"Features Editor","staff":true},"1991":{"name":"Ohrmazd","title":"","staff":false},"391727":{"name":"Ron Amadeo","title":"Reviews Editor","staff":true},"348927":{"name":"Sam Machkovech","title":"Tech Culture Editor","staff":true},"588289":{"name":"Samuel Axon","title":"Senior Reviews Editor","staff":true},"294205":{"name":"Scott K. Johnson","title":"Associate Writer","staff":true},"671621":{"name":"Steven Klein","title":"Developer","staff":false},"173910":{"name":"Timothy B. Lee","title":"Senior tech policy reporter","staff":true}},"tags":["denial-of-service-attack","exploits","minecraft","vulnerabilities"],"zen_mode":false,"vote_sentiments":[{"sentiment_id":"1","sentiment":"agree","direction":"positive","icon":null,"label":"Agree"},{"sentiment_id":"3","sentiment":"interesting","direction":"positive","icon":null,"label":"Interesting"},{"sentiment_id":"5","sentiment":"funny","direction":"positive","icon":null,"label":"Funny"},{"sentiment_id":"6","sentiment":"addsto","direction":"positive","icon":null,"label":"Adds to Story"},{"sentiment_id":"7","sentiment":"disagree","direction":"negative","icon":null,"label":"Disagree"},{"sentiment_id":"8","sentiment":"inaccurate","direction":"negative","icon":null,"label":"Inaccurate"},{"sentiment_id":"11","sentiment":"pointless","direction":"negative","icon":null,"label":"Doesn't Contribute"},{"sentiment_id":"12","sentiment":"abusive","direction":"negative","icon":null,"label":"Abusive"}]};
//]]>
</script>
<article itemscope="itemscope" itemtype="http://schema.org/NewsArticle" class="article-single standalone intro-default" id="">
<div class="column-wrapper">
<div class="left-column">
<header class="article-header">
<h4 class="post-upperdek">
Biz &amp; IT —
</h4>
<h1 itemprop="headline">
Just-released <i>Minecraft</i> exploit makes it easy to crash game servers
</h1>
<h2 itemprop="description">
Two-year-old bug exposes thousands of servers to crippling attack.
</h2>
<section class="post-meta">
<p class="byline" itemprop="author creator" itemscope="itemscope" itemtype="http://schema.org/Person">
<a itemprop="url" href="https://arstechnica.com/author/dan-goodin/" rel="author"><span itemprop="name">Dan Goodin</span></a> - <time class="date" data-time="1429214521" datetime="2015-04-16T20:02:01+00:00">Apr 16, 2015 8:02 pm UTC</time>
</p>
</section>
</header>
<section class="article-guts">
<div itemprop="articleBody" class="article-content post-page">
<figure class="intro-image intro-left">
<img src="https://cdn.arstechnica.net/wp-content/uploads/2015/04/server-crash-640x426.jpg" alt="Just-released Minecraft exploit makes it easy to crash game servers" />
<figcaption class="caption">
<div class="caption-credit">
<a rel="nofollow" class="caption-link" href="https://en.wikipedia.org/wiki/Kernel_panic#/media/File:Kernel-panic.jpg">Kevin</a>
</div>
</figcaption>
</figure>
<aside id="social-left" class="social-left" aria-label="Read the comments or share this article">
<a title="51 posters participating" class="comment-count icon-comment-bubble-down" href="https://arstechnica.com/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/?comments=1">
<h4 class="comment-count-before">
reader comments
</h4><span class="comment-count-number">75</span> <span class="visually-hidden">with 51 posters participating</span></a>
<div class="share-links">
<h4>
Share this story
</h4>
<ul>
<li>
<a href="https://www.facebook.com/sharer.php?u=https%3A%2F%2Farstechnica.com%2F%3Fpost_type%3Dpost%26p%3D648287" target="_blank" class="social-icon share-facebook" title="Share on Facebook"><span class="visually-hidden">Share on Facebook</span></a>
</li>
<li>
<a href="https://twitter.com/share?text=Just-released+%3Ci%3EMinecraft%3C%2Fi%3E+exploit+makes+it+easy+to+crash+game+servers&amp;url=https%3A%2F%2Farstechnica.com%2F%3Fpost_type%3Dpost%26p%3D648287" target="_blank" class="social-icon share-twitter" title="Share on Twitter"><span class="visually-hidden">Share on Twitter</span></a>
</li>
<li>
<a href="https://www.reddit.com/submit?url=https%3A%2F%2Farstechnica.com%2F%3Fpost_type%3Dpost%26p%3D648287&amp;title=Just-released+%3Ci%3EMinecraft%3C%2Fi%3E+exploit+makes+it+easy+to+crash+game+servers" target="_blank" class="social-icon share-reddit" title="Share on Reddit"><span class="visually-hidden">Share on Reddit</span></a>
</li>
</ul>
</div>
</aside><!-- cache miss 581:single/related:5a5daf59fa5245a64fe8615caa0b1d1b --><!-- empty -->
<p>
A flaw in the wildly popular online game <em>Minecraft</em> makes it easy for just about anyone to crash the server hosting the game, according to a computer programmer who has released proof-of-concept code that exploits the vulnerability.
</p>
<p>
"I thought a lot before writing this post," Pakistan-based developer Ammar Askar wrote in a <a href="http://blog.ammaraskar.com/minecraft-vulnerability-advisory">blog post published Thursday</a>, 21 months, he said, after privately reporting the bug to <em>Minecraft</em> developer Mojang. "On the one hand I don't want to expose thousands of servers to a major vulnerability, yet on the other hand Mojang has failed to act on it."
</p>
<p>
The bug resides in the <a href="https://github.com/ammaraskar/pyCraft">networking internals of the <em>Minecraft</em> protocol</a>. It allows the contents of inventory slots to be exchanged, so that, among other things, items in players' hotbars are displayed automatically after logging in. <em>Minecraft</em> items can also store arbitrary metadata in a file format known as <a href="http://wiki.vg/NBT">Named Binary Tag (NBT)</a>, which allows complex data structures to be kept in hierarchical nests. Askar has released <a href="https://github.com/ammaraskar/pyCraft/tree/nbt_exploit">proof-of-concept attack code</a> he said exploits the vulnerability to crash any server hosting the game. Here's how it works.
</p>
<blockquote>
<p>
The vulnerability stems from the fact that the client is allowed to send the server information about certain slots. This, coupled with the NBT formats nesting allows us to <em>craft</em> a packet that is incredibly complex for the server to deserialize but trivial for us to generate.
</p>
<p>
In my case, I chose to create lists within lists, down to five levels. This is a json representation of what it looks like.
</p>
<div class="highlight">
<pre><code class="language-javascript" data-lang="javascript"><span class="nx">rekt</span><span class="o">:</span> <span class="p">{</span>
<span class="nx">list</span><span class="o">:</span> <span class="p">[</span>
<span class="nx">list</span><span class="o">:</span> <span class="p">[</span>
<span class="nx">list</span><span class="o">:</span> <span class="p">[</span>
<span class="nx">list</span><span class="o">:</span> <span class="p">[</span>
<span class="nx">list</span><span class="o">:</span> <span class="p">[</span>
<span class="nx">list</span><span class="o">:</span> <span class="p">[</span>
<span class="p">]</span>
<span class="nx">list</span><span class="o">:</span> <span class="p">[</span>
<span class="p">]</span>
<span class="nx">list</span><span class="o">:</span> <span class="p">[</span>
<span class="p">]</span>
<span class="nx">list</span><span class="o">:</span> <span class="p">[</span>
<span class="p">]</span>
<span class="p">...</span>
<span class="p">]</span>
<span class="p">...</span>
<span class="p">]</span>
<span class="p">...</span>
<span class="p">]</span>
<span class="p">...</span>
<span class="p">]</span>
<span class="p">...</span>
<span class="p">]</span>
<span class="p">...</span>
<span class="p">}</span></code></pre>
</div>
<p>
The root of the object, <code>rekt</code>, contains 300 lists. Each list has a list with 10 sublists, and each of those sublists has 10 of their own, up until 5 levels of recursion. Thats a total of <code>10^5 * 300 = 30,000,000</code> lists.
</p>
<p>
And this isnt even the theoretical maximum for this attack. Just the nbt data for this payload is 26.6 megabytes. But luckily Minecraft implements a way to compress large packets, lucky us! zlib shrinks down our evil data to a mere 39 kilobytes.
</p>
<p>
Note: in previous versions of Minecraft, there was no protocol wide compression for big packets. Previously, NBT was sent compressed with gzip and prefixed with a signed short of its length, which reduced our maximum payload size to <code>2^15 - 1</code>. Now that the length is a varint capable of storing integers up to <code>2^28</code>, our potential for attack has increased significantly.
</p>
<p>
When the server will decompress our data, itll have 27 megs in a buffer somewhere in memory, but that isnt the bit thatll kill it. When it attempts to parse it into NBT, itll create java representations of the objects meaning suddenly, the sever is having to create several million java objects including ArrayLists. This runs the server out of memory and causes tremendous CPU load.
</p>
<p>
This vulnerability exists on almost all previous and current Minecraft versions as of 1.8.3, the packets used as attack vectors are the <a href="http://wiki.vg/Protocol#Player_Block_Placement">0x08: Block Placement Packet</a> and <a href="http://wiki.vg/Protocol#Creative_Inventory_Action">0x10: Creative Inventory Action</a>.
</p>
<p>
The fix for this vulnerability isnt exactly that hard, the client should never really send a data structure as complex as NBT of arbitrary size and if it must, some form of recursion and size limits should be implemented.
</p>
<p>
These were the fixes that I recommended to Mojang 2 years ago.
</p>
</blockquote>
<p>
Ars is asking Mojang for comment and will update this post if company officials respond.
</p>
<div id="action_button_container"></div>
</div>
</section>
</div>
<div class="xrail">
<div class="xrail-content">
<aside class="ad ad_xrail ad_xrail_top" aria-label="Top sidebar advertisement"></aside>
<aside class="ad_native ad_native_xrail" aria-label="Sidebar native advertisement"></aside>
</div>
</div>
</div>
<div class="column-wrapper">
<div class="left-column">
<div id="social-footer">
<a title="51 posters participating" class="comment-count icon-comment-bubble-down" href="https://arstechnica.com/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/?comments=1">
<h4 class="comment-count-before">
reader comments
</h4><span class="comment-count-number">75</span> <span class="visually-hidden">with 51 posters participating</span></a>
<div class="share-links">
<h4>
Share this story
</h4>
<ul>
<li>
<a href="https://www.facebook.com/sharer.php?u=https%3A%2F%2Farstechnica.com%2F%3Fpost_type%3Dpost%26p%3D648287" target="_blank" class="social-icon share-facebook" title="Share on Facebook"><span class="visually-hidden">Share on Facebook</span></a>
</li>
<li>
<a href="https://twitter.com/share?text=Just-released+%3Ci%3EMinecraft%3C%2Fi%3E+exploit+makes+it+easy+to+crash+game+servers&amp;url=https%3A%2F%2Farstechnica.com%2F%3Fpost_type%3Dpost%26p%3D648287" target="_blank" class="social-icon share-twitter" title="Share on Twitter"><span class="visually-hidden">Share on Twitter</span></a>
</li>
<li>
<a href="https://www.reddit.com/submit?url=https%3A%2F%2Farstechnica.com%2F%3Fpost_type%3Dpost%26p%3D648287&amp;title=Just-released+%3Ci%3EMinecraft%3C%2Fi%3E+exploit+makes+it+easy+to+crash+game+servers" target="_blank" class="social-icon share-reddit" title="Share on Reddit"><span class="visually-hidden">Share on Reddit</span></a>
</li>
</ul>
</div>
</div><!-- cache hit 581:single/author:2814756d09510ff24ad530ca37a5a9a9 -->
<section class="article-author">
<a style="background-image:url('https://cdn.arstechnica.net/wp-content/uploads/2018/10/Dang.jpg');" class="author-photo" href="/author/dan-goodin" tabindex="-1" role="presentation" aria-hidden="true"></a>
<div class="author-bio">
<section class="author-bio-top">
<a href="/author/dan-goodin" class="author-name">Dan Goodin</a> Dan is the Security Editor at Ars Technica, which he joined in 2012 after working for The Register, the Associated Press, Bloomberg News, and other publications.
</section>
<section class="author-social">
<strong>Email</strong> <a href="mailto:dan.goodin@arstechnica.com">dan.goodin@arstechnica.com</a> <span class="slashes">//</span> <strong>Twitter</strong> <a href="https://www.twitter.com/dangoodin001" target="_blank">@dangoodin001</a>
</section>
</div>
</section>
</div>
<div class="xrail"></div>
</div>
<div id="article-footer-wrap">
<aside class="ad ad_fullwidth fullwidth" aria-label="Full width advertisement"></aside>
<section id="comments-area" class="comments-area column-wrapper">
<div class="row comments-row left-column">
<a name="comments-bar" id="comments-bar"></a>
<div id="comments-container"></div>
<div id="comments-posting-container" class="thick-divide-bottom">
<p id="reply">
You must <a href="https://arstechnica.com/civis/ucp.php?mode=login&amp;return_to/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/" class="vote_login">login or create an account</a> to comment.
</p>
</div>
</div>
<div class="xrail xrail-comments">
<div class="xrail-content xrail-content-comments">
<aside class="ad ad_xrail ad_xrail_comments" aria-label="Comments sidebar advertisement"></aside>
</div>
</div>
</section>
<section class="inline-playlist">
<div class="ars-video-playlist">
<h3 class="ars-video-playlist-module-header">
Channel <span>Ars Technica</span>
</h3>
<div class="ars-video-playlist-module" data-playlist-id="arstechnica-channel-ars-information-technology" data-video-options="[]"></div>
</div>
</section>
<div class="prev-next-links">
<a href="https://arstechnica.com/tech-policy/2015/04/dozens-of-us-government-online-whistleblower-sites-not-secured-by-https/" rel="prev"><span class="arrow"></span> Previous story</a> <a href="https://arstechnica.com/gaming/2015/04/hidden-files-suggest-street-fighters-ryu-may-come-to-smash-bros/" rel="next">Next story <span class="arrow"></span></a>
</div>
<footer id="article-footer">
<div id="recommendations-footer">
<div id="story-recommendations">
<div class="heading-column">
<h3>
Related Stories
</h3>
</div>
<ul id="story-recs" class="rec-wrap"></ul>
</div>
<div id="sponsored-recommendations">
<div class="heading-column">
<h3>
Sponsored Stories
</h3><a href="http://www.outbrain.com/what-is/default/en" target="_blank">Powered by </a>
</div>
<ul id="outbrain-recs"></ul>
</div>
<div id="latest-stories">
<div class="heading-column">
<h3>
Today on Ars
</h3>
</div>
<ul id="latest-recs" class="rec-wrap"></ul>
</div>
</div>
</footer>
</div>
</article>
</main>
<footer class="site-footer">
<nav class="nav-footer">
<section>
<ul>
<li>
<a href="/store/">Store</a>
</li>
<li>
<a href="/store/product/subscriptions/">Subscribe</a>
</li>
<li>
<a href="/about-us/">About Us</a>
</li>
<li>
<a href="/rss-feeds/">RSS Feeds</a>
</li>
<li>
<a rel="nofollow" href="http://arstechnica.com/information-technology/2015/04/just-released-minecraft-exploit-makes-it-easy-to-crash-game-servers/?view=mobile">View Mobile Site</a>
</li>
</ul>
</section>
<section>
<ul>
<li>
<a href="/contact-us/">Contact Us</a>
</li>
<li>
<a href="/staff-directory/">Staff</a>
</li>
<li>
<a href="/advertise-with-us/">Advertise with us</a>
</li>
<li>
<a href="/reprints/">Reprints</a>
</li>
</ul>
</section>
<section class="footer-newsletter">
<div class="newsletter-wrapper">
<h3>
<a href="/newsletters/">Newsletter Signup</a>
</h3>
<p>
Join the Ars Orbital Transmission mailing list to get weekly updates delivered to your inbox.
</p><a class="button" href="/newsletters/">Sign me up →</a>
</div>
</section>
</nav>
<section class="footer-terms-logo">
<div class="cn-logo">
<a href="http://condenast.com/" class="icon icon-logo-cn-us" title="Visit Condé Nast"></a>
</div>
<p id="copyright-terms">
CNMN Collection<br />
WIRED Media Group<br />
© 2020 Condé Nast. All rights reserved. Use of and/or registration on any portion of this site constitutes acceptance of our <a href="https://www.condenast.com/user-agreement/">User Agreement</a> (updated 1/1/20) and <a href="https://www.condenast.com/privacy-policy/">Privacy Policy and Cookie Statement</a> (updated 1/1/20) and <a href="/amendment-to-conde-nast-user-agreement-privacy-policy/">Ars Technica Addendum</a> (effective 8/21/2018). Ars may earn compensation on sales from links on this site. <a href="/affiliate-link-policy/">Read our affiliate link policy</a>.<br />
<a href="https://www.condenast.com/privacy-policy/#california">Your California Privacy Rights</a> | <a id="ot-sdk-btn" class="ot-sdk-show-settings">Do Not Sell My Personal Information</a><br />
The material on this site may not be reproduced, distributed, transmitted, cached or otherwise used, except with the prior written permission of Condé Nast.<br />
<a href="https://www.condenast.com/online-behavioral-advertising-oba-and-how-to-opt-out-of-oba/#clickheretoreadmoreaboutonlinebehavioraladvertising(oba)">Ad Choices</a>
</p>
</section>
</footer>
</div>
<script type="text/javascript" src="https://cdn.arstechnica.net/wp-content/themes/ars/assets/js/main-fafdd8b108.js"></script> <!-- cache hit 581:single/javascript-footer:5a5daf59fa5245a64fe8615caa0b1d1b -->
<script async="async" type="application/javascript" src="https://embed.actionbutton.co/widget/widget.min.js"></script> <!-- Parse.ly start -->
<script id="parsely-cfg" src="//fpa-cdn.arstechnica.com/keys/arstechnica.com/p.js"></script> <!-- Parse.ly end -->
<script src="https://player.cnevids.com/interlude/arstechnica.js" async="async"></script>
<script id="conde-polar" src="https://cdn.mediavoice.com/nativeads/script/condenastcorporate/conde-asa-polar-master.js" async="async"></script>
<script>
<![CDATA[
(function () {
function DQ() {
var queue = window.sparrowQueue;
this.push = fn => fn();
window.sparrowQueue = this;
while (queue.length) {
queue.shift()();
}
}
function e(t, e) {
var n, a, o;
a = !1, n = document.createElement("script"), n.type = "text/javascript", n.src = t, n.onload = n.onreadystatechange = function () {
a || this.readyState && "complete" != this.readyState || (a = !0, e ? e() : !0)
}, o = document.getElementsByTagName("script")[0], o.parentNode.insertBefore(n, o)
}
if (location.search.indexOf('no_sparrow') < 0) {
e("https://pixel.condenastdigital.com/config/v2/production/ars-technica.config.js", function () {
e("https://pixel.condenastdigital.com/sparrow.min.js", function () {
if (window.SparrowConfigV2) {
window.sparrow = new window.Sparrow(window.SparrowConfigV2);
new DQ();
}
})
})
}
})();
]]>
</script>
<script type="text/javascript" src="//s.skimresources.com/js/100098X1555750.skimlinks.js"></script>
</body>
</html>

@ -1,8 +0,0 @@
{
"title": "Base URL with base relative test",
"byline": null,
"dir": null,
"excerpt": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\n quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\n cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\n proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"siteName": null,
"readerable": false
}

@ -1,22 +0,0 @@
<div id="readability-page-1" class="page">
<article>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p>
<p>Links</p>
<p><a href="http://fakehost/test/base/foo/bar/baz.html">link</a></p>
<p><a href="http://fakehost/test/base/foo/bar/baz.html">link</a></p>
<p><a href="http://fakehost/foo/bar/baz.html">link</a></p>
<p><a href="http://fakehost/test/base/#foo">link</a></p>
<p><a href="http://fakehost/test/base/baz.html#foo">link</a></p>
<p><a href="http://fakehost/foo/bar/baz.html#foo">link</a></p>
<p><a href="http://test/foo/bar/baz.html">link</a></p>
<p><a href="https://test/foo/bar/baz.html">link</a></p>
<p>Images</p>
<p><img src="http://fakehost/test/base/foo/bar/baz.png" /></p>
<p><img src="http://fakehost/test/base/foo/bar/baz.png" /></p>
<p><img src="http://fakehost/foo/bar/baz.png" /></p>
<p><img src="http://test/foo/bar/baz.png" /></p>
<p><img src="https://test/foo/bar/baz.png" /></p>
<h2>Foo</h2>
<p> Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p>
</article>
</div>

@ -1,44 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<base href="base/"/>
<title>Base URL with base relative test</title>
</head>
<body>
<article>
<h1>Lorem</h1>
<div>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
<p>Links</p>
<p><a href="foo/bar/baz.html">link</a></p>
<p><a href="./foo/bar/baz.html">link</a></p>
<p><a href="/foo/bar/baz.html">link</a></p>
<p><a href="#foo">link</a></p>
<p><a href="baz.html#foo">link</a></p>
<p><a href="/foo/bar/baz.html#foo">link</a></p>
<p><a href="http://test/foo/bar/baz.html">link</a></p>
<p><a href="https://test/foo/bar/baz.html">link</a></p>
<p>Images</p>
<p><img src="foo/bar/baz.png"/></p>
<p><img src="./foo/bar/baz.png"/></p>
<p><img src="/foo/bar/baz.png"/></p>
<p><img src="http://test/foo/bar/baz.png"/></p>
<p><img src="https://test/foo/bar/baz.png"/></p>
<h2>Foo</h2>
<div>
Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
</article>
</body>
</html>

@ -1,8 +0,0 @@
{
"title": "Base URL with base test",
"byline": null,
"dir": null,
"excerpt": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\n quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\n cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\n proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"siteName": null,
"readerable": false
}

@ -1,22 +0,0 @@
<div id="readability-page-1" class="page">
<article>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p>
<p>Links</p>
<p><a href="http://fakehost/foo/bar/baz.html">link</a></p>
<p><a href="http://fakehost/foo/bar/baz.html">link</a></p>
<p><a href="http://fakehost/foo/bar/baz.html">link</a></p>
<p><a href="http://fakehost/#foo">link</a></p>
<p><a href="http://fakehost/baz.html#foo">link</a></p>
<p><a href="http://fakehost/foo/bar/baz.html#foo">link</a></p>
<p><a href="http://test/foo/bar/baz.html">link</a></p>
<p><a href="https://test/foo/bar/baz.html">link</a></p>
<p>Images</p>
<p><img src="http://fakehost/foo/bar/baz.png" /></p>
<p><img src="http://fakehost/foo/bar/baz.png" /></p>
<p><img src="http://fakehost/foo/bar/baz.png" /></p>
<p><img src="http://test/foo/bar/baz.png" /></p>
<p><img src="https://test/foo/bar/baz.png" /></p>
<h2>Foo</h2>
<p> Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p>
</article>
</div>

@ -1,44 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<base href="/"/>
<title>Base URL with base test</title>
</head>
<body>
<article>
<h1>Lorem</h1>
<div>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
<p>Links</p>
<p><a href="foo/bar/baz.html">link</a></p>
<p><a href="./foo/bar/baz.html">link</a></p>
<p><a href="/foo/bar/baz.html">link</a></p>
<p><a href="#foo">link</a></p>
<p><a href="baz.html#foo">link</a></p>
<p><a href="/foo/bar/baz.html#foo">link</a></p>
<p><a href="http://test/foo/bar/baz.html">link</a></p>
<p><a href="https://test/foo/bar/baz.html">link</a></p>
<p>Images</p>
<p><img src="foo/bar/baz.png"/></p>
<p><img src="./foo/bar/baz.png"/></p>
<p><img src="/foo/bar/baz.png"/></p>
<p><img src="http://test/foo/bar/baz.png"/></p>
<p><img src="https://test/foo/bar/baz.png"/></p>
<h2>Foo</h2>
<div>
Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
</article>
</body>
</html>

@ -1,8 +1,6 @@
{
"title": "Base URL test",
"byline": null,
"dir": null,
"excerpt": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\n quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\n cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\n proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"siteName": null,
"readerable": false
}

@ -1,22 +1,42 @@
<div id="readability-page-1" class="page">
<article>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Links</p>
<p><a href="http://fakehost/test/foo/bar/baz.html">link</a></p>
<p><a href="http://fakehost/test/foo/bar/baz.html">link</a></p>
<p><a href="http://fakehost/foo/bar/baz.html">link</a></p>
<p><a href="#foo">link</a></p>
<p><a href="http://fakehost/test/baz.html#foo">link</a></p>
<p><a href="http://fakehost/foo/bar/baz.html#foo">link</a></p>
<p><a href="http://test/foo/bar/baz.html">link</a></p>
<p><a href="https://test/foo/bar/baz.html">link</a></p>
<p><a href="http://fakehost/test/foo/bar/baz.html">link</a>
</p>
<p><a href="http://fakehost/test/foo/bar/baz.html">link</a>
</p>
<p><a href="http://fakehost/foo/bar/baz.html">link</a>
</p>
<p><a href="http://test/foo/bar/baz.html">link</a>
</p>
<p><a href="https://test/foo/bar/baz.html">link</a>
</p>
<p>Images</p>
<p><img src="http://fakehost/test/foo/bar/baz.png" /></p>
<p><img src="http://fakehost/test/foo/bar/baz.png" /></p>
<p><img src="http://fakehost/foo/bar/baz.png" /></p>
<p><img src="http://test/foo/bar/baz.png" /></p>
<p><img src="https://test/foo/bar/baz.png" /></p>
<h2>Foo</h2>
<p> Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p>
<p>
<img src="http://fakehost/test/foo/bar/baz.png">
</p>
<p>
<img src="http://fakehost/test/foo/bar/baz.png">
</p>
<p>
<img src="http://fakehost/foo/bar/baz.png">
</p>
<p>
<img src="http://test/foo/bar/baz.png">
</p>
<p>
<img src="https://test/foo/bar/baz.png">
</p>
<p>Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</article>
</div>

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta charset="utf-8">
<title>Base URL test</title>
</head>
<body>
@ -19,9 +19,6 @@
<p><a href="foo/bar/baz.html">link</a></p>
<p><a href="./foo/bar/baz.html">link</a></p>
<p><a href="/foo/bar/baz.html">link</a></p>
<p><a href="#foo">link</a></p>
<p><a href="baz.html#foo">link</a></p>
<p><a href="/foo/bar/baz.html#foo">link</a></p>
<p><a href="http://test/foo/bar/baz.html">link</a></p>
<p><a href="https://test/foo/bar/baz.html">link</a></p>
<p>Images</p>

@ -1,8 +1,6 @@
{
"title": "Basic tag cleaning test",
"byline": null,
"dir": null,
"excerpt": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n tempor incididunt ut labore et dolore magna aliqua.",
"siteName": null,
"readerable": true
}

@ -1,11 +1,19 @@
<div id="readability-page-1" class="page">
<div>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.</p>
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
<div>
<p>Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</div>

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta charset="utf-8">
<title>Basic tag cleaning test</title>
</head>
<body>
@ -26,7 +26,7 @@
<object data="foo.swf" type="application/x-shockwave-flash" width="88" height="31">
<param movie="foo.swf" />
</object>
<embed src="foo.swf"/>
<embed src="foo.swf">
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>

@ -1,8 +0,0 @@
{
"title": "Obama admits US gun laws are his 'biggest frustration'",
"byline": null,
"dir": null,
"excerpt": "President Barack Obama tells the BBC his failure to pass \"common sense gun safety laws\" is the greatest frustration of his presidency.",
"siteName": "BBC News",
"readerable": true
}

@ -1,62 +0,0 @@
<div id="readability-page-1" class="page">
<div property="articleBody">
<p>President Barack Obama has admitted that his failure to pass "common sense gun safety laws" in the US is the greatest frustration of his presidency. </p>
<p>In an interview with the BBC, Mr Obama said it was "distressing" not to have made progress on the issue "even in the face of repeated mass killings".</p>
<p>He vowed to keep trying, but the BBC's North America editor Jon Sopel said the president did not sound very confident. </p>
<p>However, Mr Obama said race relations had improved during his presidency. </p>
<p>Hours after the interview, a gunman opened fire at a cinema in the US state of Louisiana, killing two people and injuring several others before shooting himself.</p>
<p>In a wide-ranging interview, President Obama also said:</p>
<ul>
<li>
<a href="http://www.bbc.co.uk/news/uk-politics-33647154">The UK must stay in the EU</a> to have influence on the world stage
</li>
<li>He is confident the Iran nuclear deal will be passed by Congress </li>
<li>Syria needs a political solution in order to defeat the Islamic State group</li>
<li>He would speak "bluntly" against corruption <a href="http://www.bbc.co.uk/news/world-us-canada-33646563">and human rights violations in Kenya</a>
</li>
<li>He would defend his advocacy of gay rights following protests in Kenya</li>
<li>Despite racial tensions, the US is becoming more diverse and more tolerant</li>
</ul>
<p><a href="http://www.bbc.co.uk/news/world-us-canada-33646542">Read the full transcript of his interview</a></p>
<p>Mr Obama lands in Kenya later on Friday for his first visit since becoming president. </p>
<p>But with just 18 months left in power, he said gun control was the area where he has been "most frustrated and most stymied" since coming to power in 2009.</p>
<p>"If you look at the number of Americans killed since 9/11 by terrorism, it's less than 100. If you look at the number that have been killed by gun violence, it's in the tens of thousands," Mr Obama said. </p>
<figure><img src="http://ichef.bbci.co.uk/news/555/cpsprodpb/462D/production/_84456971_gettyimages-167501087.jpg" datasrc="http://ichef.bbci.co.uk/news/976/cpsprodpb/462D/production/_84456971_gettyimages-167501087.jpg" alt="Gun control campaigners protest in McPhearson Square in Washington DC - 25 April 2013" height="549" width="976" />
<figcaption>
<span> The president said he would continue fighting for greater gun control laws </span>
</figcaption>
</figure>
<p>"For us not to be able to resolve that issue has been something that is distressing," he added. </p>
<p>Mr Obama has pushed for stricter gun control throughout his presidency but has been unable to secure any significant changes to the laws. </p>
<p>After nine African-American churchgoers were killed in South Carolina in June, he admitted "politics in this town" meant there were few options available.</p>
<figure><img src="http://ichef.bbci.co.uk/news/555/media/images/76020000/jpg/_76020974_line976.jpg" datasrc="http://ichef.bbci.co.uk/news/464/media/images/76020000/jpg/_76020974_line976.jpg" alt="line" height="2" width="464" /></figure>
<h2>Analysis: Jon Sopel, BBC News, Washington</h2>
<figure><img src="http://ichef-1.bbci.co.uk/news/555/cpsprodpb/6D3D/production/_84456972_p072315al-0500.jpg" datasrc="http://ichef-1.bbci.co.uk/news/976/cpsprodpb/6D3D/production/_84456972_p072315al-0500.jpg" alt="President Barack Obama participates in an interview with Jon Sopel of BBC in the Roosevelt Room of the White House - 23 July 2015" height="549" width="976" /></figure>
<p>Nine months ago, the president seemed like a spent force, after taking a beating in the midterm elections, during which members of his own party were reluctant to campaign on his record. </p>
<p>But the man sat before me today was relaxed and confident, buoyed by a string of "wins" on healthcare, Cuba and Iran, after bitter and ongoing battles with his many critics. </p>
<p>The only body swerve the president performed was when I asked him <a href="http://www.bbc.co.uk/news/world-us-canada-33643168"> how many minds he had changed on the Iran nuclear deal </a>after an intense sell aimed at Gulf allies and members of US Congress who remain implacably opposed. </p>
<p>There was a momentary flicker across the president's face as if to say "You think you got me?" before his smile returned and he proceeded to talk about how Congress would come round.</p>
<p>But notably, he did not give a direct answer to that question, which leaves me with the impression that he has persuaded precisely zero.</p>
<p><a href="http://www.bbc.co.uk/news/world-us-canada-33646875">Five things we learned from Obama interview</a></p>
<p><a href="http://www.bbc.co.uk/news/world-us-canada-33646545">The presidential body swerve</a></p>
<figure><img src="http://ichef.bbci.co.uk/news/555/media/images/76020000/jpg/_76020974_line976.jpg" datasrc="http://ichef.bbci.co.uk/news/464/media/images/76020000/jpg/_76020974_line976.jpg" alt="line" height="2" width="464" /></figure>
<p>On race relations, Mr Obama said recent concerns around policing and mass incarcerations were "legitimate and deserve intense attention" but insisted progress had been made. </p>
<p>Children growing up during the eight years of his presidency "will have a different view of race relations in this country and what's possible," he said. </p>
<p>"There are going to be tensions that arise. But if you look at my daughters' generation, they have an attitude about race that's entirely different than even my generation."</p>
<p>Talking about how he was feeling after his recent successes, he said "every president, every leader has strengths and weaknesses". </p>
<p>"One of my strengths is I have a pretty even temperament. I don't get too high when it's high and I don't get too low when it's low," he said. </p>
<figure><img src="http://ichef-1.bbci.co.uk/news/555/cpsprodpb/142FD/production/_84458628_shirtreuters.jpg" datasrc="http://ichef-1.bbci.co.uk/news/976/cpsprodpb/142FD/production/_84458628_shirtreuters.jpg" alt="Customer looks at Obama shirts at a stall in Nairobi&apos;s Kibera slums, 23 July 2015" height="549" width="976" />
<figcaption>
<span> Kenya is getting ready to welcome the US president </span>
</figcaption>
</figure>
<h2>Kenya trip</h2>
<p>Mr Obama was speaking to the BBC at the White House before departing for Kenya.</p>
<p>His father was Kenyan and the president is expected to meet relatives in Nairobi.</p>
<p>Mr Obama has faced criticism in the country after the US legalised gay marriage. However, in his interview, the president said he would not fall silent on the issue.</p>
<p>"I am not a fan of discrimination and bullying of anybody on the basis of race, on the basis of religion, on the basis of sexual orientation or gender," he said.</p>
<p>The president also admitted that some African governments, including Kenya's, needed to improve their records on human rights and democracy. However, he defended his decision to engage with and visit those governments. </p>
<p>"Well, they're not ideal institutions. But what we found is, is that when we combined blunt talk with engagement, that gives us the best opportunity to influence and open up space for civil society." </p>
<p>Mr Obama will become the first US president to address the African Union when he travels on to Ethiopia on Sunday.</p>
</div>
</div>

File diff suppressed because one or more lines are too long

@ -1,8 +0,0 @@
{
"title": "Open Verilog flow for Silego GreenPak4 programmable logic devices",
"byline": null,
"dir": "ltr",
"excerpt": "I've written a couple of posts in the past few months but they were all for the blog at work so I figured I'm long overdue for one on Silic...",
"siteName": null,
"readerable": true
}

@ -1,117 +0,0 @@
<div id="readability-page-1" class="page">
<div id="post-body-932306423056216142" itemprop="description articleBody">
<p> I've written a couple of posts in the past few months but they were all for <a href="http://blog.ioactive.com/search/label/Andrew%20Zonenberg">the blog at work</a> so I figured I'm long overdue for one on Silicon Exposed.</p>
<h2> So what's a GreenPak?</h2>
<p> Silego Technology is a fabless semiconductor company located in the SF Bay area, which makes (among other things) a line of programmable logic devices known as GreenPak. Their <a href="http://www.silego.com/products/greenpak5.html">5th generation parts</a> were just announced, but I started this project before that happened so I'm still targeting the <a href="http://www.silego.com/products/greenpak4.html">4th generation</a>.</p>
<p> GreenPak devices are kind of like itty bitty <a href="http://www.cypress.com/products/32-bit-arm-cortex-m-psoc">PSoCs</a> - they have a mixed signal fabric with an ADC, DACs, comparators, voltage references, plus a digital LUT/FF fabric and some typical digital MCU peripherals like counters and oscillators (but no CPU).</p>
<p> It's actually an interesting architecture - FPGAs (including some devices marketed as CPLDs) are a 2D array of LUTs connected via wires to adjacent cells, and true (product term) CPLDs are a star topology of AND-OR arrays connected by a crossbar. GreenPak, on the other hand, is a star topology of LUTs, flipflops, and analog/digital hard IP connected to a crossbar.</p>
<p> Without further ado, here's a block diagram showing all the cool stuff you get in the SLG46620V:</p>
<table>
<tbody>
<tr>
<td>
<a href="https://1.bp.blogspot.com/-YIPC5jkXkDE/Vy7YPSqFKWI/AAAAAAAAAxI/a7D6Ji2GxoUvcrwUkI4RLZcr2LFQEJCTACLcB/s1600/block-diagram.png" imageanchor="1"><img height="512" src="https://1.bp.blogspot.com/-YIPC5jkXkDE/Vy7YPSqFKWI/AAAAAAAAAxI/a7D6Ji2GxoUvcrwUkI4RLZcr2LFQEJCTACLcB/s640/block-diagram.png" width="640" /></a>
</td>
</tr>
<tr>
<td>SLG46620V block diagram (from device datasheet)</td>
</tr>
</tbody>
</table>
<p> They're also tiny (the SLG46620V is a 20-pin 0.4mm pitch STQFN measuring 2x3 mm, and the lower gate count SLG46140V is a mere 1.6x2 mm) and probably the cheapest programmable logic device on the market - $0.50 in low volume and less than $0.40 in larger quantities.</p>
<p> The Vdd range of GreenPak4 is huge, more like what you'd expect from an MCU than an FPGA! It can run on anything from 1.8 to 5V, although performance is only specified at 1.8, 3.3, and 5V nominal voltages. There's also a dual-rail version that trades one of the GPIO pins for a second power supply pin, allowing you to interface to logic at two different voltage levels.</p>
<p> To support low-cost/space-constrained applications, they even have the configuration memory on die. It's one-time programmable and needs external Vpp to program (presumably Silego didn't want to waste die area on charge pumps that would only be used once) but has a SRAM programming mode for prototyping.</p>
<p> The best part is that the development software (GreenPak Designer) is free of charge and provided for all major operating systems including Linux! Unfortunately, the only supported design entry method is schematic entry and there's no way to write your design in a HDL.</p>
<p> While schematics may be fine for quick tinkering on really simple designs, they quickly get unwieldy. The nightmare of a circuit shown below is just a bunch of counters hooked up to LEDs that blink at various rates.</p>
<table>
<tbody>
<tr>
<td>
<a href="https://1.bp.blogspot.com/-k3naUT3uXao/Vy7WFac246I/AAAAAAAAAw8/mePy_ostO8QJra5ZJrbP2WGhTlJ0B_r8gCLcB/s1600/schematic-from-hell.png" imageanchor="1"><img height="334" src="https://1.bp.blogspot.com/-k3naUT3uXao/Vy7WFac246I/AAAAAAAAAw8/mePy_ostO8QJra5ZJrbP2WGhTlJ0B_r8gCLcB/s640/schematic-from-hell.png" width="640" /></a>
</td>
</tr>
<tr>
<td>Schematic from hell!</td>
</tr>
</tbody>
</table>
<p> As if this wasn't enough of a problem, the largest GreenPak4 device (the SLG46620V) is split into two halves with limited routing between them, and the GUI doesn't help the user manage this complexity at all - you have to draw your schematic in two halves and add "cross connections" between them.</p>
<p> The icing on the cake is that schematics are a pain to diff and collaborate on. Although GreenPak schematics are XML based, which is a touch better than binary, who wants to read a giant XML diff and try to figure out what's going on in the circuit?</p>
<p> This isn't going to be a post on the quirks of Silego's software, though - that would be boring. As it turns out, there's one more exciting feature of these chips that I didn't mention earlier: the configuration bitstream is 100% documented in the device datasheet! This is unheard of in the programmable logic world. As Nick of Arachnid Labs <a href="http://www.arachnidlabs.com/blog/2015/03/30/greenpak/">says</a>, the chip is "just dying for someone to write a VHDL or Verilog compiler for it". As you can probably guess by from the title of this post, I've been busy doing exactly that.</p>
<h2> Great! How does it work?</h2>
<p> Rather than wasting time writing a synthesizer, I decided to write a GreenPak technology library for Clifford Wolf's excellent open source synthesis tool, <a href="http://www.clifford.at/yosys/">Yosys</a>, and then make a place-and-route tool to turn that into a final netlist. The post-PAR netlist can then be loaded into GreenPak Designer in order to program the device.</p>
<p> The first step of the process is to run the "synth_greenpak4" Yosys flow on the Verilog source. This runs a generic RTL synthesis pass, then some coarse-grained extraction passes to infer shift register and counter cells from behavioral logic, and finally maps the remaining logic to LUT/FF cells and outputs a JSON-formatted netlist.</p>
<p> Once the design has been synthesized, my tool (named, surprisingly, gp4par) is then launched on the netlist. It begins by parsing the JSON and constructing a directed graph of cell objects in memory. A second graph, containing all of the primitives in the device and the legal connections between them, is then created based on the device specified on the command line. (As of now only the SLG46620V is supported; the SLG46621V can be added fairly easily but the SLG46140V has a slightly different microarchitecture which will require a bit more work to support.)</p>
<p> After the graphs are generated, each node in the netlist graph is assigned a numeric label identifying the type of cell and each node in the device graph is assigned a list of legal labels: for example, an I/O buffer site is legal for an input buffer, output buffer, or bidirectional buffer.</p>
<table>
<tbody>
<tr>
<td>
<a href="https://2.bp.blogspot.com/-kIekczO693g/Vy7dBqYifXI/AAAAAAAAAxc/hMNJBs5bedIQOrBzzkhq4gbmhR-n58EQwCLcB/s1600/graph-labels.png" imageanchor="1"><img height="141" src="https://2.bp.blogspot.com/-kIekczO693g/Vy7dBqYifXI/AAAAAAAAAxc/hMNJBs5bedIQOrBzzkhq4gbmhR-n58EQwCLcB/s400/graph-labels.png" width="400" /></a>
</td>
</tr>
<tr>
<td>Example labeling for a subset of the netlist and device graphs</td>
</tr>
</tbody>
</table>
<p> The labeled nodes now need to be placed. The initial placement uses a simple greedy algorithm to create a valid (although not necessarily optimal or even routable) placement:</p>
<ol>
<li>Loop over the cells in the netlist. If any cell has a LOC constraint, which locks the cell to a specific physical site, attempt to assign the node to the specified site. If the specified node is the wrong type, doesn't exist, or is already used by another constrained node, the constraint is invalid so fail with an error.</li>
<li>Loop over all of the unconstrained cells in the netlist and assign them to the first unused site with the right label. If none are available, the design is too big for the device so fail with an error.</li>
</ol>
<p> Once the design is placed, the placement optimizer then loops over the design and attempts to improve it. A simulated annealing algorithm is used, where changes to the design are accepted unconditionally if they make the placement better, and with a random, gradually decreasing probability if they make it worse. The optimizer terminates when the design receives a perfect score (indicating an optimal placement) or if it stops making progress for several iterations. Each iteration does the following:</p>
<ol>
<li>Compute a score for the current design based on the number of unroutable nets, the amount of routing congestion (number of nets crossing between halves of the device), and static timing analysis (not yet implemented, always zero).</li>
<li>Make a list of nodes that contributed to this score in some way (having some attached nets unroutable, crossing to the other half of the device, or failing timing).</li>
<li>Remove nodes from the list that are LOC'd to a specific location since we're not allowed to move them.</li>
<li>Remove nodes from the list that have only one legal placement in the device (for example, oscillator hard IP) since there's nowhere else for them to go.</li>
<li>Pick a node from the remainder of the list at random. Call this our pivot.</li>
<li>Find a list of candidate placements for the pivot: </li>
<ol>
<li>Consider all routable placements in the other half of the device.</li>
<li>If none were found, consider all routable placements anywhere in the device.</li>
<li>If none were found, consider all placements anywhere in the device even if they're not routable.</li>
</ol>
<li>Pick one of the candidates at random and move the pivot to that location. If another cell in the netlist is already there, put it in the vacant site left by the pivot.</li>
<li>Re-compute the score for the design. If it's better, accept this change and start the next iteration.</li>
<li>If the score is worse, accept it with a random probability which decreases as the iteration number goes up. If the change is not accepted, restore the previous placement.</li>
</ol>
<p> After optimization, the design is checked for routability. If any edges in the netlist graph don't correspond to edges in the device graph, the user probably asked for something impossible (for example, trying to hook a flipflop's output to a comparator's reference voltage input) so fail with an error.</p>
<p> The design is then routed. This is quite simple due to the crossbar structure of the device. For each edge in the netlist:</p>
<ol>
<li>If dedicated (non-fabric) routing is used for this path, configure the destination's input mux appropriately and stop.</li>
<li>If the source and destination are in the same half of the device, configure the destination's input mux appropriately and stop.</li>
<li>A cross-connection must be used. Check if we already used one to bring the source signal to the other half of the device. If found, configure the destination to route from that cross-connection and stop.</li>
<li>Check if we have any cross-connections left going in this direction. If they're all used, the design is unroutable due to congestion so fail with an error.</li>
<li>Pick the next unused cross-connection and configure it to route from the source. Configure the destination to route from the cross-connection and stop.</li>
</ol>
<p> Once routing is finished, run a series of post-PAR design rule checks. These currently include the following:</p>
<ul>
<li>If any node has no loads, generate a warning</li>
<li>If an I/O buffer is connected to analog hard IP, fail with an error if it's not configured in analog mode.</li>
<li>Some signals (such as comparator inputs and oscillator power-down controls) are generated by a shared mux and fed to many loads. If different loads require conflicting settings for the shared mux, fail with an error.</li>
</ul>
<p> If DRC passes with no errors, configure all of the individual cells in the netlist based on the HDL parameters. Fail with an error if an invalid configuration was requested.</p>
<p> Finally, generate the bitstream from all of the per-cell configuration and write it to a file.</p>
<h2> Great, let's get started!</h2>
<p> If you don't already have one, you'll need to buy a <a href="http://www.silego.com/buy/index.php?main_page=product_info&amp;products_id=388">GreenPak4 development kit</a>. The kit includes samples of the SLG46620V (among other devices) and a programmer/emulation board. While you're waiting for it to arrive, install <a href="http://www.silego.com/softdoc/software.html">GreenPak Designer</a>.</p>
<p> Download and install Yosys. Although Clifford is pretty good at merging my pull requests, only <a href="https://github.com/azonenberg/yosys/">my fork on Github</a> is guaranteed to have the most up-to-date support for GreenPak devices so don't be surprised if you can't use a bleeding-edge feature with mainline Yosys.</p>
<p> Download and install gp4par. You can get it from <a href="https://github.com/azonenberg/openfpga/">the Github repository</a>.</p>
<p> Write your HDL, compile with Yosys, P&amp;R with gp4par, and import the bitstream into GreenPak Designer to program the target device. The most current gp4par manual is included in LaTeX source form in the source tree and is automatically built as part of the compile process. If you're just browsing, there's a <a href="http://thanatos.virtual.antikernel.net/unlisted/gp4-hdl.pdf">relatively recent PDF version</a> on my web server.</p>
<p> If you'd like to see the Verilog that produced the nightmare of a schematic I showed above, <a href="https://github.com/azonenberg/openfpga/blob/master/tests/greenpak4/Blinky/Blinky.v">here it is</a>.</p>
<p> Be advised that this project is still very much a work in progress and there are still a number of SLG46620V features I don't support (see the manual for exact details).</p>
<h2> I love it / it segfaulted / there's a problem in the manual!</h2>
<p> Hop in our IRC channel (##openfpga on Freenode) and let me know. Feedback is great, pull requests are even better,</p>
<h2> You're competing with Silego's IDE. Have they found out and sued you yet?</h2>
<p> Nope. They're fully aware of what I'm doing and are rolling out the red carpet for me. They love the idea of a HDL flow as an alternative to schematic entry and are pretty amazed at how fast it's coming together.</p>
<p> After I reported a few bugs in their datasheets they decided to skip the middleman and give me direct access to the engineer who writes their documentation so that I can get faster responses. The last time I found a problem (two different parts of the datasheet contradicted each other) an updated datasheet was in my inbox and on their website by the next day. I only wish Xilinx gave me that kind of treatment!</p>
<p> They've even <a href="https://twitter.com/SilegoTech/status/717018987771469824">offered me free hardware</a> to help me add support for their latest product family, although I plan to get GreenPak4 support to a more stable state before taking them up on the offer.</p>
<h2> So what's next?</h2>
<p> Better testing, for starters. I have to verify functionality by hand with a DMM and oscilloscope, which is time consuming.</p>
<p> My contact at Silego says they're going to be giving me documentation on the SRAM emulation interface soon, so I'm going to make a hardware-in-loop test platform that connects to my desktop and the Silego ZIF socket, and lets me load new bitstreams via a scriptable interface. It'll have FPGA-based digital I/O as well as an ADC and DAC on every device pin, plus an adjustable voltage regulator for power, so I can feed in arbitrary mixed-signal test waveforms and write PC-based unit tests to verify correct behavior.</p>
<p> Other than that, I want to finish support for the SLG46620V in the next month or two. The SLG46621V will be an easy addition since only one pin and the relevant configuration bits have changed from the 46620 (I suspect they're the same die, just bonded out differently).</p>
<p> Once that's done I'll have to do some more extensive work to add the SLG46140V since the architecture is a bit different (a lot of the combinatorial logic is merged into multi-function blocks). Luckily, the 46140 has a lot in common architecturally with the GreenPak5 family, so once that's done GreenPak5 will probably be a lot easier to add support for.</p>
<p> My thanks go out to Clifford Wolf, whitequark, the IRC users in ##openfpga, and everyone at Silego I've worked with to help make this possible. I hope that one day this project will become mature enough that Silego will ship it as an officially supported extension to GreenPak Designer, making history by becoming the first modern programmable logic vendor to ship a fully open source synthesis and P&amp;R suite. </p>
</div>
</div>

File diff suppressed because it is too large Load Diff

@ -1,8 +0,0 @@
{
"title": "'Neutral' Snopes Fact-Checker David Emery: 'Are There Any Un-Angry Trump Supporters?' - Breitbart",
"byline": "by Lucas Nolan22 Dec 2016651",
"dir": "ltr",
"excerpt": "Snopes fact checker and staff writer David Emery posted to Twitter asking if there were “any un-angry Trump supporters?”",
"siteName": "Breitbart",
"readerable": true
}

@ -1,27 +0,0 @@
<div id="readability-page-1" class="page">
<div>
<figure>
<div>
<p><img itemprop="image" src="http://media.breitbart.com/media/2016/11/GettyImages-621866810-640x480.jpg" alt="Supporters of Republican presidential nominee Donald Trump cheer during election night at the New York Hilton Midtown in New York on November 9, 2016. / AFP / JIM WATSON (Photo credit should read JIM WATSON/AFP/Getty Images)" width="640" height="480" /></p>
<p>JIM WATSON/AFP/Getty Images</p>
</div>
</figure>
<time datetime="2016-12-22T10:43:37Z">22 Dec, 2016</time>
<time datetime="2016-12-22T18:59:12Z">22 Dec, 2016</time>
</div>
<div>
<div id="EmailOptin">
<p><span>SIGN UP</span> FOR OUR NEWSLETTER</p>
</div>
<h2><span>Snopes fact checker and staff writer David Emery posted to Twitter asking if there were “any un-angry Trump supporters?”</span></h2>
<p><span>Emery, a writer for partisan “fact-checking” website Snopes.com which soon will be in charge of labelling </span><a href="http://www.breitbart.com/tech/2016/12/15/facebook-introduce-warning-labels-stories-deemed-fake-news/"><span>“fake news”</span></a><span> alongside ABC News and Politifact, retweeted an article by Vulture magazine relating to the </span><a href="http://www.breitbart.com/big-hollywood/2016/11/19/boycotthamilton-trends-hamilton-cast-members-harass-mike-pence/"><span>protests</span></a><span> of the <em>Hamilton</em> musical following the decision by the cast of the show to make a </span><a href="http://www.breitbart.com/big-hollywood/2016/11/19/tolerance-hamilton-cast-lectures-mike-pence-broadway-stage/"><span>public announcement</span></a><span> to Vice-president elect Mike Pence while he watched the performance with his family.</span></p>
<div id="EmailOptinM">
<p><span>SIGN UP</span> FOR OUR NEWSLETTER</p>
</div>
<p><span>The tweet from Vulture magazine reads, “</span><a href="https://twitter.com/hashtag/Hamilton?src=hash" target="_blank" rel="noopener"><span>#Hamilton</span></a><span> Chicago show interrupted by angry Trump supporter.” Emery retweeted the story, saying, “Are there un-angry Trump supporters?”</span></p>
<p><span>This isnt the first time the Snopes.com writer has expressed anti-Trump sentiment on his Twitter page. In another tweet in which Emery links to an article that falsely attributes a quote to President-elect Trump, Emery states, “Incredibly, some people actually think they have to put words in Trumps mouth to make him look bad.”</span></p>
<p><span>Emery also retweeted an article by <em>New York</em> magazine that claimed President-elect Trump relied on lies to win during his campaign and that we now lived in a “post-truth” society. “Before long well all have forgotten what it was like to live in the same universe; or maybe we already have,” Emery tweeted.</span></p>
<p><span>Facebook believe that Emery, along with other Snopes writers, ABC News, and </span><a href="http://www.breitbart.com/tech/2016/12/16/flashback-weekly-standard-data-shows-politifact-has-it-out-for-republicans/"><span>Politifact</span></a><span> are impartial enough to label and silence what they believe to be “fake news” on social media. </span></p>
<p><i><span>Lucas Nolan is a reporter for Breitbart Tech covering issues of free speech and online censorship. Follow him on Twitter </span></i><a href="http://twitter.com/lucasnolan_" target="_blank" rel="noopener"><i><span>@LucasNolan_</span></i></a><i><span> or email him at </span></i><a href="http://www.breitbart.com/wp-admin/blank"><i><span>lnolan@breitbart.com</span></i></a></p>
</div>
</div>

File diff suppressed because one or more lines are too long

@ -1,8 +0,0 @@
{
"title": "Seven secrets that hotel owners don't want you to know",
"byline": "Hazel Sheffield",
"dir": null,
"excerpt": "Most people go to hotels for the pleasure of sleeping in a giant bed with clean white sheets and waking up to fresh towels in the morning. But those towels and sheets might not be as clean as they look, according to the hotel bosses that responded to an online thread about the things hotel owners dont want you to know.",
"siteName": "The Independent",
"readerable": true
}

@ -1,67 +0,0 @@
<div id="readability-page-1" class="page">
<div itemprop="articleBody" id="gigya-share-btns-2_gig_containerParent">
<p>Most people go to hotels for the pleasure of sleeping in a giant bed with clean white sheets and waking up to fresh towels in the morning.</p>
<p>But those towels and sheets might not be as clean as they look, according to the hotel bosses that responded to an online thread about the things hotel owners dont want you to know.</p>
<p>Zeev Sharon and Michael Forrest Jones both run hotel start-ups in the US. Forrest Jones runs the start-up Beechmont Hotels Corporation, a hotel operating company that consults with hotel owners on how they can improve their business. Sharon is the CEO of Hotelied, a start-up that allows people to sign up for discounts at luxury hotels.</p>
<p>But even luxury hotels arent always cleaned as often as they should be.</p>
<p>Here are some of the secrets that the receptionist will never tell you when you check in, according to answers posted on <a href="https://www.quora.com/What-are-the-things-we-dont-know-about-hotel-rooms" target="_blank">Quora</a>.</p>
<div>
<div>
<p><img src="https://static.independent.co.uk/s3fs-public/styles/story_medium/public/thumbnails/image/2014/03/18/10/bandb2.jpg" alt="bandb2.jpg" title="bandb2.jpg" width="564" height="423" /></p>
</div>
<p>Even posh hotels might not wash a blanket in between stays </p>
</div>
<p>1. Take any blankets or duvets off the bed</p>
<p>Forrest Jones said that anything that comes into contact with any of the previous guests skin should be taken out and washed every time the room is made, but that even the fanciest hotels dont always do so. "Hotels are getting away from comforters. Blankets are here to stay, however. But some hotels are still hesitant about washing them every day if they think they can get out of it," he said.</p>
<div>
<p>Video shows bed bug infestation at New York hotel</p>
</div>
<div>
<div>
<p><img src="https://static.independent.co.uk/s3fs-public/styles/story_medium/public/thumbnails/image/2015/05/26/11/hotel-door-getty.jpg" alt="hotel-door-getty.jpg" title="hotel-door-getty.jpg" width="564" height="423" /></p>
</div>
<p>Forrest Jones advised stuffing the peep hole with a strip of rolled up notepaper when not in use. </p>
</div>
<p>2. Check the peep hole has not been tampered with</p>
<p>This is not common, but can happen, Forrest Jones said. He advised stuffing the peep hole with a strip of rolled up notepaper when not in use. When someone knocks on the door, the paper can be removed to check who is there. If no one is visible, he recommends calling the front desk immediately. “I look forward to the day when I can tell you to choose only hotels where every employee who has access to guestroom keys is subjected to a complete public records background check, prior to hire, and every year or two thereafter. But for now, I can't,” he said.</p>
<div>
<div>
<p><img src="https://static.independent.co.uk/s3fs-public/styles/story_medium/public/thumbnails/image/2013/07/31/15/luggage-3.jpg" alt="luggage-3.jpg" title="luggage-3.jpg" width="564" height="423" /></p>
</div>
<p>Put luggage on the floor </p>
</div>
<p>3. Dont use a wooden luggage rack</p>
<p>Bedbugs love wood. Even though a wooden luggage rack might look nicer and more expensive than a metal one, its a breeding ground for bugs. Forrest Jones says guests should put the items they plan to take from bags on other pieces of furniture and leave the bag on the floor.</p>
<div>
<div>
<p><img src="https://static.independent.co.uk/s3fs-public/styles/story_medium/public/thumbnails/image/2015/04/13/11/Lifestyle-hotels.jpg" alt="Lifestyle-hotels.jpg" title="Lifestyle-hotels.jpg" width="564" height="423" /></p>
</div>
<p>The old rule of thumb is that for every 00 invested in a room, the hotel should charge in average daily rate </p>
</div>
<p>4. Hotel rooms are priced according to how expensive they were to build</p>
<p>Zeev Sharon said that the old rule of thumb is that for every $1000 invested in a room, the hotel should charge $1 in average daily rate. So a room that cost $300,000 to build, should sell on average for $300/night.</p>
<h3>5. Beware the wall-mounted hairdryer</h3>
<p>It contains the most germs of anything in the room. Other studies have said the TV remote and bedside lamp switches are the most unhygienic. “Perhaps because it's something that's easy for the housekeepers to forget to check or to squirt down with disinfectant,” Forrest Jones said.</p>
<div data-scald-gallery="3739501">
<h2><span></span>Business news in pictures</h2>
</div>
<h3>6. Mini bars almost always lose money</h3>
<p>Despite the snacks in the minibar seeming like the most overpriced food you have ever seen, hotel owners are still struggling to make a profit from those snacks. "Minibars almost always lose money, even when they charge $10 for a Diet Coke,” Sharon said.</p>
<div>
<div>
<p><img src="https://static.independent.co.uk/s3fs-public/styles/story_medium/public/thumbnails/image/2014/03/13/16/agenda7.jpg" alt="agenda7.jpg" title="agenda7.jpg" width="564" height="423" /></p>
</div>
<p>Towels should always be cleaned between stays </p>
</div>
<p>7. Always made sure the hand towels are clean when you arrive</p>
<p>Forrest Jones made a discovery when he was helping out with the housekeepers. “You know where you almost always find a hand towel in any recently-vacated hotel room that was occupied by a guy? On the floor, next to the bed, about halfway down, maybe a little toward the foot of the bed. Same spot in the floor, next to almost every bed occupied by a man, in every room. I'll leave the rest to your imagination,” he said.</p>
<meta itemprop="datePublished" content="2016-05-08T10:11:51+01:00" />
<ul>
<li> More about: </li>
<li><a itemprop="keywords" href="http://fakehost/topic/Hotels">Hotels</a></li>
<li><a itemprop="keywords" href="http://fakehost/topic/Hygiene">Hygiene</a></li>
</ul>
<p><a href="http://fakehost/syndication/reuse-permision-form?url=http://www.independent.co.uk/news/business/news/seven-secrets-that-hotel-owners-dont-want-you-to-know-10506160.html" target="_blank"><img src="http://fakehost/sites/all/themes/ines_themes/independent_theme/img/reuse.png" width="25" />Reuse content</a>
</p>
</div>
</div>

File diff suppressed because it is too large Load Diff

@ -1,8 +0,0 @@
{
"title": "Student Dies After Diet Pills She Bought Online \"Burned Her Up From Within\"",
"byline": null,
"dir": null,
"excerpt": "An inquest into Eloise Parry's death has been adjourned until July.",
"siteName": "BuzzFeed",
"readerable": true
}

@ -1,40 +0,0 @@
<div id="readability-page-1" class="page">
<div id="buzz_sub_buzz">
<div id="superlist_3758406_5547137" rel:buzz_num="1">
<h2>The mother of a woman who took suspected diet pills bought online has described how her daughter was “literally burning up from within” moments before her death.</h2>
<p> <span>West Merica Police</span></p>
</div>
<div id="superlist_3758406_5547213" rel:buzz_num="2">
<p>Eloise Parry, 21, was taken to Royal Shrewsbury hospital on 12 April after taking a lethal dose of highly toxic “slimming tablets”. </p>
<p>“The drug was in her system, there was no anti-dote, two tablets was a lethal dose and she had taken eight,” her mother, Fiona, <a href="https://www.westmercia.police.uk/article/9501/A-tribute-to-Eloise-Aimee-Parry-written-by-her-mother-Fiona-Parry">said in a statement</a> yesterday.</p>
<p>“As Eloise deteriorated, the staff in A&amp;E did all they could to stabilise her. As the drug kicked in and started to make her metabolism soar, they attempted to cool her down, but they were fighting an uphill battle.</p>
<p>“She was literally burning up from within.”</p>
<p>She added: “They never stood a chance of saving her. She burned and crashed.”</p>
</div>
<div id="superlist_3758406_5547140" rel:buzz_num="3">
<div>
<div>
<p><img src="http://ak-hdl.buzzfed.com/static/2015-04/21/5/enhanced/webdr12/grid-cell-2501-1429608056-15.jpg" rel:bf_image_src="http://ak-hdl.buzzfed.com/static/2015-04/21/5/enhanced/webdr12/grid-cell-2501-1429608056-15.jpg" height="412" width="203" /></p>
</div>
<p>Facebook</p>
</div>
<div>
<div>
<p><img src="http://ak-hdl.buzzfed.com/static/2015-04/21/5/enhanced/webdr12/grid-cell-2501-1429608057-18.jpg" rel:bf_image_src="http://ak-hdl.buzzfed.com/static/2015-04/21/5/enhanced/webdr12/grid-cell-2501-1429608057-18.jpg" height="412" width="412" /></p>
</div>
<p>Facebook</p>
</div>
</div>
<div id="superlist_3758406_5547284" rel:buzz_num="4">
<p>West Mercia police <a href="https://www.westmercia.police.uk/article/9500/Warning-Issued-As-Shrewsbury-Woman-Dies-After-Taking-Suspected-Diet-Pills">said the tablets were believed to contain dinitrophenol</a>, known as DNP, which is a highly toxic industrial chemical. </p>
<p>“We are undoubtedly concerned over the origin and sale of these pills and are working with partner agencies to establish where they were bought from and how they were advertised,” said chief inspector Jennifer Mattinson from the West Mercia police.</p>
<p>The Food Standards Agency warned people to stay away from slimming products that contained DNP.</p>
<p>“We advise the public not to take any tablets or powders containing DNP, as it is an industrial chemical and not fit for human consumption,” it said in a statement.</p>
</div>
<div id="superlist_3758406_5547219" rel:buzz_num="5">
<h2>Fiona Parry issued a plea for people to stay away from pills containing the chemical.</h2>
<p>“[Eloise] just never really understood how dangerous the tablets that she took were,” she said. “Most of us dont believe that a slimming tablet could possibly kill us.</p>
<p>“DNP is not a miracle slimming pill. It is a deadly toxin.”</p>
</div>
</div>
</div>

File diff suppressed because one or more lines are too long

@ -1,8 +0,0 @@
{
"title": "The Modern Ambitions Behind Neon",
"byline": "Sarah Archer",
"dir": null,
"excerpt": "The once-ubiquitous form of lighting was novel when it first emerged in the early 1900s, though it has since come to represent decline.",
"siteName": "CityLab",
"readerable": true
}

@ -1,68 +0,0 @@
<div id="readability-page-1" class="page">
<article itemscope="itemscope" itemtype="https://schema.org/NewsArticle">
<meta itemprop="datePublished" content="2019-04-30T13:39:00-04:00" />
<meta itemprop="dateModified" content="2019-04-30T13:40:00-04:00" />
<meta itemprop="mainEntityOfPage" content="https://www.citylab.com/design/2019/04/neon-signage-20th-century-history/588400/" />
<figure itemprop="image" itemscope="itemscope" itemtype="http://schema.org/ImageObject">
<picture>
<source srcset="https://cdn.citylab.com/media/img/citylab/2019/04/mr1/940.jpg?mod=1556645448" media="(min-width: 1024px)" />
<source srcset="https://cdn.citylab.com/media/img/citylab/2019/04/mr1/lead_large.jpg?mod=1556645448" media="(min-width: 576px)" />
</picture>
<meta itemprop="height" content="128" />
<meta itemprop="width" content="300" />
<meta itemprop="url" content="https://cdn.citylab.com/media/img/citylab/2019/04/mr1/300.jpg?mod=1556645448" />
<picture>
<source srcset="https://cdn.citylab.com/media/img/citylab/2019/04/mr1/300.jpg?mod=1556645448" media="(max-width: 575px)" /><img src="https://cdn.citylab.com/media/img/citylab/2019/04/mr1/300.jpg?mod=1556645448" alt="" srcset="https://cdn.citylab.com/media/img/citylab/2019/04/mr1/300.jpg?mod=1556645448" />
</picture>
<figcaption>
<span itemprop="caption">The Moulin Rouge cabaret in Paris</span> <span itemprop="creator">Benoit Tessier/Reuters</span>
</figcaption>
</figure>
<h2 itemprop="description"> The once-ubiquitous form of lighting was novel when it first emerged in the early 1900s, though it has since come to represent decline. </h2>
<section id="article-section-1">
<p> In the summer of 1898, the Scottish chemist Sir William Ramsay made a discovery that would eventually give the Moulin Rouge in Paris, the Las Vegas Strip, and New Yorks Times Square their perpetual nighttime glow. Using the boiling point of argon as a reference point, Ramsay and his colleague Morris W. Travers isolated three more noble gases and gave them evocative Greek names: neon, krypton, and xenon. In so doing, the scientists bestowed a label of permanent novelty on the most famous of the trio—neon, which translates as “new.” This discovery was the foundation on which the French engineer Georges Claude crafted a new form of illumination over the next decade. He designed glass tubes in which neon gas could be trapped, then electrified, to create a light that glowed reliably for more than 1,000 hours. </p>
<p> In the 2012 book <em>Lêtre et le Néon</em>, <a href="https://mitpress.mit.edu/books/being-and-neonness-translation-and-content-revised-augmented-and-updated-edition-luis-de-miranda">which has been newly translated into English by Michael Wells</a>, the philosopher Luis de Miranda weaves a history of neon lighting as both artifact and metaphor. <em>Being and Neonness</em>, as the book is called in its English edition, isnt a typical material history. There are no photographs. Even de Mirandas own example of a neon deli sign spotted in Paris is re-created typographically, with text in all caps and dashes forming the border of the sign, as one might attempt on Twitter. Fans of Miami Beachs restored Art Deco hotels and Californias bowling alleys might be disappointed by the lack of glossy historical images. Nonetheless, de Miranda makes a convincing case for neon as a symbol of the grand modern ambitions of the 20th century. </p>
<p> De Miranda beautifully evokes the notion of neon lighting as an icon of the 1900s in his introduction: “When we hear the word <em>neon</em>, an image pops into our heads: a combination of light, colors, symbols, and glass. This image is itself a mood. It carries an atmosphere. It speaks … of the essence of cities, of the poetry of nights, of the 20th century.” When neon lights debuted in Europe, they seemed dazzlingly futuristic. But their husky physicality started becoming obsolete by the 1960s, thanks in part to the widespread use of plastic for fluorescent signs. Neon signs exist today, though theyve been eclipsed by newer technologies such as digital billboards, and they remain charmingly analog: Signs must be made by hand because theres no cost-effective way to mass-produce them. </p>
<p> In the 1910s, neon started being used for cosmopolitan flash in Paris at precisely the time and place where the first great modernist works were being created. De Mirandas recounting of the ingenuity emerging from the French capital a century ago is thrilling to contemplate: the cubist art of Pablo Picasso, the radically deconstructed fashions of Coco Chanel, the stream-of-consciousness poetry of Gertrude Stein, and the genre-defying music of Claude Debussy—all of which heralded a new age of culture for Europe and for the world. </p>
</section>
<section id="article-section-2">
<p> Amid this artistic groundswell, Georges Claude premiered his neon lights at the <a href="https://www.mondial-paris.com/en/visiteur/auto">Paris Motor Show</a> in December 1910, captivating visitors with 40-foot-tall tubes affixed to the buildings exterior. The lights shone orange-red because neon, by itself, produces that color. <em>Neon lighting</em> is a catchall term that describes the technology of glass tubing that contains gas or chemicals that glow when electrified. For example, neon fabricators use carbon dioxide to make white, and mercury to make blue. Claude acknowledged at the time that neon didnt produce the ideal color for a standard light bulb and insisted that it posed no commercial threat to incandescent bulbs. </p>
<p> Of course, the very quality that made neon fixtures a poor choice for interior lighting made them perfect for signs, de Miranda notes. The first of the neon signs was switched on in 1912, advertising a barbershop on Pariss Boulevard Montmartre, and eventually they were adopted by cinemas and nightclubs. While Claude had a monopoly on neon lighting throughout the 1920s, the leaking of trade secrets and the expiration of a series of patents broke his hold on the rapidly expanding technology. </p>
</section>
<section id="article-section-3">
<p> In the following decades, neons nonstop glow and vibrant colors turned ordinary buildings and surfaces into 24/7 billboards for businesses, large and small, that wanted to convey a sense of always being open. The first examples of neon in the United States debuted in Los Angeles, where the Packard Motor Car Company commissioned two large blue-and-orange <span>Packard</span> signs that literally stopped traffic because they distracted motorists. The lighting also featured heavily at the Chicago Century of Progress Exposition in 1933 and at the 1939 Worlds Fair in New York. At the latter event, a massive neon sign reading <span>Futurama</span> lit the way to a General Motors exhibition that heralded “The World of Tomorrow.” </p>
<figure>
<picture><img alt="" data-srcset="https://cdn.theatlantic.com/assets/media/img/posts/2019/04/AP_8912060228/cbd32b0e1.jpg" src="https://cdn.theatlantic.com/assets/media/img/posts/2019/04/AP_8912060228/cbd32b0e1.jpg" /></picture>
<figcaption class="caption"> Workers remove a hammer and sickle from a neon sign that reads “Glory to Communism,” visible on the roof of the Communist-run electricity-board headquarters in Czechoslovakia in 1989. (AP) </figcaption>
</figure>
<p> De Miranda points out that businesses werent alone in embracing neons ability to spread messages effectively. By the middle of the century, the lighting was being adopted for more political purposes. “In the 1960s, the Soviets deployed a vast neonization of the Eastern bloc capitals to emulate capitalist metropolises,” de Miranda writes. “Because consumer shops were rare in the Polish capital [of Warsaw], they did not hesitate to illuminate the façades of public buildings.” In other words, as opposed to the sole use of the more obvious forms of propaganda via posters or slogans, the mass introduction of neon lighting was a way of getting citizens of Communist cities to see their surroundings with the pizzazz and nighttime glamour of major Western capitals. </p>
</section>
<section id="article-section-4">
<p> Neon, around this time, began to be phased out, thanks to cheaper and less labor-intensive alternatives. In addition, the global economic downturn of the 1970s yielded a landscape in which older, flickering neon signs, which perhaps their owners couldnt afford to fix or replace, came to look like symbols of decline. Where such signs were once sophisticated and novel, they now seemed dated and even seedy. </p>
<section>
<h2> Cities are changing fast. Keep up with the <b>CityLab Daily</b> newsletter. </h2><label for="promo-email-input-email">The best way to follow issues you care about.</label>
</section>
<p> De Miranda understands this evolution by zooming out and looking at the 1900s as the “neon century.” The author draws a parallel between the physical form of neon lights, which again are essentially containers for electrified gases, and that of a glass capsule—suggesting they are a kind of message in a bottle from a time before the First World War. “Since then, [neon lights] have witnessed all the transformations that have created the world we live in,” de Miranda writes. “Today, they sometimes seem to maintain a hybrid status, somewhere between junkyards and museums, not unlike European capitals themselves.” </p>
<figure>
<picture><img alt="" data-srcset="https://cdn.theatlantic.com/assets/media/img/posts/2019/04/AP_945361213236/888fdd750.jpg" src="https://cdn.theatlantic.com/assets/media/img/posts/2019/04/AP_945361213236/888fdd750.jpg" /></picture>
<figcaption class="caption"> Martin Wartman, a student at Northern Kentucky University, works on a neon sign at the Neonworks of Cincinnati workshop connected to the American Sign Museum, in 2016. (John Minchillo / AP) </figcaption>
</figure>
<p> Another mark of neons hybridity: Its obsolescence started just as some contemporary artists began using the lights in their sculptures. Bruce Naumans 1968 work <em><a href="https://www.stedelijk.nl/en/collection/1097-bruce-nauman-my-name-as-though-it-were-written-on-the-surface-of-the-moon">My Name as Though It Were Written on the Surface of the Moon</a></em> poked fun at the space race—another symbol of 20th-century technological innovation whose moment has passed. The piece uses blue “neon” letters (mercury, actually) to spell out the name “bruce” in lowercase cursive, with each character repeated several times as if to convey a person speaking slowly in outer space. The British artist Tracey Emin has made <a href="https://www.artsy.net/collection/tracey-emin-neon-sculptures-and-prints">sculptures</a> that resemble neon Valentines Day candies: They read as garish and sentimental confections with pink, heart-shaped frames that surround blue text fragments. Drawing on the nostalgia-inducing quality of neon, the sculptures messages are redolent of old-fashioned movie dialogue, with titles such as “You Loved Me Like a Distant Star” and “The Kiss Was Beautiful.” </p>
<p> Seeing neon lighting tamed in the context of a gallery display fits comfortably with de Mirandas notion that neon technology is like a time capsule from another age. In museums, works of neon art and design coexist with objects that were ahead of their own time in years past—a poignant fate for a technology that made its name advertising “The World of Tomorrow.” Yet today neon is also experiencing a kind of craft revival. The fact that it cant be mass-produced has made its fabrication something akin to a cherished artisanal technique. Bars and restaurants hire firms such as Let There Be Neon in Manhattan, or <a href="https://www.instagram.com/theneonqueen/">the L.A.-based master neon artist Lisa Schulte</a>, to create custom signs and works of art. Neons story even continues to glow from inside museums such as Californias <a href="https://www.neonmona.org/">Museum of Neon Art</a> and the Neon Museum in Las Vegas. If it can still be a vital medium for artists and designers working today, “neonness” need not only be trapped in the past. It might also capture the mysterious glow of the near future—just as it did a century ago. </p>
<p>
<em>This article originally appeared on <a href="https://www.theatlantic.com/entertainment/archive/2019/04/being-and-neonness-neon-lights-symbol-20th-century/588184/">The Atlantic</a>.</em>
</p>
</section>
<section data-include="css:https://cdn.citylab.com/static/a/frontend/dist/citylab/css/components/author-article.cf4e8e0b143f.css">
<h4> About the Author </h4>
<div itemprop="author" itemscope="itemscope" itemtype="http://schema.org/Person">
<h5 itemprop="name">
<a href="https://www.citylab.com/authors/sarah-archer/">Sarah Archer</a>
</h5>
<p itemprop="description">
<a href="https://www.citylab.com/authors/sarah-archer/" data-omni-click="inherit">Sarah Archer</a> is the author of <em>The Midcentury Kitchen</em>.
</p>
</div>
</section>
</article>
</div>

File diff suppressed because one or more lines are too long

@ -1,8 +1,6 @@
{
"title": "Bartleby the Scrivener Web Study Text",
"byline": null,
"dir": null,
"excerpt": "Ere introducing the scrivener, as he first appeared to me, it is fit \n I make some mention of myself, my employees, my business, my chambers, \n and general surroundings; because some such description is indispensable \n to an adequate understanding of the chief character about to be presented.",
"siteName": null,
"readerable": true
}

File diff suppressed because it is too large Load Diff

@ -4,10 +4,10 @@
<head>
<title>Bartleby the Scrivener Web Study Text</title>
<meta http-equiv="resource-type"
content="document"/>
<meta http-equiv="copyright" content="Copyright Ann Woodlief"/>
<meta name="generator" content="Me :)"/>
<LINK REL=" stylesheet "=" " type="text/css " href="http://www.vcu.edu/engweb/webtexts/myweb.css "/>
content="document">
<meta http-equiv="copyright" content="Copyright Ann Woodlief">
<meta name="generator" content="Me :)>
<LINK REL=" stylesheet "=" " type="text/css " href="http://www.vcu.edu/engweb/webtexts/myweb.css ">
<style type="text/css ">
<!--
body { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12pt;}
@ -29,11 +29,11 @@ body { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12pt;}
<td>
<h3 align="center ">Study Webtext</h3>
<h2 align="center "><font face="Lucida Handwriting " color="Maroon
">"Bartleby the Scrivener: A Story of Wall-Street " </font>(1853)&nbsp;<br/>
">"Bartleby the Scrivener: A Story of Wall-Street " </font>(1853)&nbsp;<br>
Herman Melville</h2>
<h2 align="center "><a href="http://www.vcu.edu/engweb/webtexts/bartleby.html
" target="_blank "><img src="hmhome.gif " alt="To the story text without notes
" height="38 " width="38 " align="absmiddle "/></a>
" height="38 " width="38 " align="absmiddle "></a>
</h2>
<h3 align="center ">Prepared by <a href="http://www.vcu.edu/engweb ">Ann
Woodlief,</a> Virginia Commonwealth University</h3>
@ -1847,15 +1847,15 @@ body { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12pt;}
</center>
<p></p>
<center>
<img src="../bar.gif" width="535" height="15" border="0" align="middle"/>
<img src="../bar.gif" width="535" height="15" border="0" align="middle">
<p>
<a href="http://www.vcu.edu/engweb/webtexts/bartleby/bartleby.html">Text Without Notes</a>
<br/> <a href="http://www.vcu.edu/engweb/webtexts/bartleby/web-hm.htm">Melville Web Links</a>
<br> <a href="http://www.vcu.edu/engweb/webtexts/bartleby/web-hm.htm">Melville Web Links</a>
</p>
<p>
<img src="../bar.gif" width="535" height="15" border="0" align="middle"/>
<img src="../bar.gif" width="535" height="15" border="0" align="middle">
</p>
<p> <a href="http://www.vcu.edu/engweb"><img src="../myhome.jpg" width="50" height="21" border="0" align="middle"/></a>
<p> <a href="http://www.vcu.edu/engweb"><img src="../myhome.jpg" width="50" height="21" border="0" align="middle"></a>
</p>
</center>
</body>

@ -1,8 +0,0 @@
{
"title": "Twitter Lite se estrena en México, Venezuela y otros nueve países",
"byline": "César Salza",
"dir": null,
"excerpt": "Twitter Lite llega a 11 países de América Latina, para ayudar a los usuarios con mala señal de sus redes móviles.",
"siteName": "CNET en Español",
"readerable": true
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,8 +0,0 @@
{
"title": "Zuckerberg offers peek at Facebook's acquisition strategies",
"byline": "Steven Musil",
"dir": null,
"excerpt": "Facebook CEO says be a friend and have a shared vision, but scare them when you have to and move fast.",
"siteName": "CNET",
"readerable": true
}

@ -1,29 +0,0 @@
<div id="readability-page-1" class="page">
<div itemprop="articleBody" data-component="lazyloadImages">
<figure section="shortcodeImage"><span><span itemprop="image" itemscope="" itemtype="https://schema.org/ImageObject"><img src="https://cnet1.cbsistatic.com/img/nAMdBzIE1ogVw5bOBZBaiJCt3Ro=/570x0/2014/03/21/863df5d9-e8b8-4b38-851b-5e3f77f2cf0e/mark-zuckerberg-facebook-home-10671610x407.jpg" alt="" width="570" height="0" />
<meta itemprop="url" content="https://cnet1.cbsistatic.com/img/nAMdBzIE1ogVw5bOBZBaiJCt3Ro=/570x0/2014/03/21/863df5d9-e8b8-4b38-851b-5e3f77f2cf0e/mark-zuckerberg-facebook-home-10671610x407.jpg" />
<meta itemprop="height" content="0" />
<meta itemprop="width" content="570" />
</span></span>
<figcaption><span class="caption">
<p>Facebook CEO Mark Zuckerberg, the man with the acquisition plan.</p>
</span><span>Photo by James Martin/CNET </span></figcaption>
</figure>
<p>Anyone who has ever been involved in closing a billion-dollar acquisition deal will tell you that you don't go in without a clear, well thought out plan.</p>
<p>Facebook CEO Mark Zuckerberg knows a thing or two about how to seal the deal on blockbuster buys. After all, he's the man behind his company's <a href="https://www.cnet.com/news/facebook-closes-19-billion-deal-for-whatsapp/" target="_blank">$19 billion acquisition</a> of WhatsApp, he <a href="https://www.cnet.com/news/zuckerberg-did-1-billion-instagram-deal-on-his-own/" target="_blank">personally brokered</a> its $1 billion buyout of <a href="https://www.cnet.com/news/why-facebook-plunked-down-1-billion-to-buy-instagram/" target="_blank">Instagram</a> and closed the <a href="https://www.cnet.com/news/facebook-to-buy-oculus-for-2-billion/" target="_blank">$3 billion deal</a> to buy Oculus VR.</p>
<p>Zuckerberg offered a primer on the strategies he and his company employ when they see an attractive target during testimony Tuesday <a href="https://www.cnet.com/news/zenimax-sues-oculus-over-virtual-reality-rift-tech/">in a lawsuit with ZeniMax Media</a>, which accuses Oculus and Facebook of "misappropriating" trade secrets and copyright infringement. At the heart of the lawsuit is technology that helped create liftoff for virtual reality, one of the <a href="http://www.cbsnews.com/videos/the-reality-of-the-virtual-world/" target="_blank" data-component="externalLink">hottest gadget trends today.</a></p>
<p>A key Facebook approach is building a long-term relationship with your target, Zuckerberg said at the trial. These deals don't just pop up over night, he said according to a transcript reviewed by <a href="http://www.businessinsider.com/mark-zuckerberg-explains-facebooks-acquisition-strategy-2017-1" target="_blank" data-component="externalLink">Business Insider</a>. They take time to cultivate. </p>
<blockquote>
<p>I've been building relationships, at least in Instagram and the WhatsApp cases, for years with the founders and the people that are involved in these companies, which made [it] so that when it became time or when we thought it was the right time to move, we felt like we had a good amount of context and had good relationships so that we could move quickly, which was competitively important and why a lot of these acquisitions, I think, came to us instead of our competitors and ended up being very good acquisitions over time that a lot of competitors wished they had gotten instead. </p>
</blockquote>
<p> He also stressed the need assure your target that you have a shared vision about how you will collaborate after the deal is put to bed. Zuckerberg said this was reason Facebook was able to acquire Oculus for less than its original $4 billion asking price.</p>
<blockquote>If this [deal] is going to happen, it's not going to be because we offer a lot of money, although we're going to have to offer a fair price for the company that is more than what they felt like they could do on their own. But they also need to feel like this was actually going to help their mission.</blockquote>
<p>When that doesn't work, Zuckerberg said scare tactics is an effective, if undesirable, way of persuading small startups that they face a better chance of survival if they have Facebook to guide their way rather than going it alone.</p>
<blockquote>That's less my thing, but I think if you are trying to help convince people that they want to join you, helping them understand all the pain that they would have to go through to build it out independently is a valuable tactic. </blockquote>
<p>It also pays to be weary of competing suitors for your startup, Zuckerberg said, and be willing to move fast to stave off rivals and get the deal done.</p>
<blockquote>Often, if a company knows we're offering something, they will offer more. So being able to move quickly not only increases our chance of being able to get a deal done if we want to, but it makes it so we don't have end up having to pay a lot more because the process drags out.</blockquote>
<p>It wasn't clear why these strategies didn't work on Snapchat CEO Evan Spiegel, who <a href="https://www.cnet.com/news/snapchat-said-to-rebuff-3-billion-offer-from-facebook/">famously rebuffed</a> a $3 billion takeover offer from Facebook in 2013.</p>
<p><em><strong>Tech Enabled:</strong> CNET chronicles tech's role in providing new kinds of accessibility. Check it out <a href="https://www.cnet.com/tech-enabled/">here</a>.</em><em><strong><br /></strong></em></p>
<p><em><strong>Technically Literate:</strong> Original works of short fiction with unique perspectives on tech, exclusively on CNET. <a href="https://www.cnet.com/technically-literate/">Here</a>.</em></p>
</div>
</div>

File diff suppressed because one or more lines are too long

@ -1,8 +0,0 @@
{
"title": "The 'birth lottery' and economic mobility",
"byline": "Ahiza Garcia",
"dir": null,
"excerpt": "A recently-released report on poverty and inequality found that the U.S. ranks the lowest among countries with welfare states.",
"siteName": "CNNMoney",
"readerable": true
}

@ -1,30 +0,0 @@
<div id="readability-page-1" class="page">
<div id="storytext">
<h2>The U.S. has long been heralded as a land of opportunity -- a place where anyone can succeed regardless of the economic class they were born into.</h2>
<p> But a new report released on Monday by <a href="http://web.stanford.edu/group/scspi-dev/cgi-bin/" target="_blank">Stanford University's Center on Poverty and Inequality</a> calls that into question. </p>
<p> The report assessed poverty levels, income and wealth inequality, economic mobility and unemployment levels among 10 wealthy countries with social welfare programs. </p>
<div id="smartassetcontainer">
<p> Powered by SmartAsset.com </p>
</div>
<p> Among its key findings: the class you're born into matters much more in the U.S. than many of the other countries. </p>
<p> As the <a href="http://web.stanford.edu/group/scspi-dev/cgi-bin/publications/state-union-report" target="_blank">report states</a>: "[T]he birth lottery matters more in the U.S. than in most well-off countries." </p>
<p> But this wasn't the only finding that suggests the U.S. isn't quite living up to its reputation as a country where everyone has an equal chance to get ahead through sheer will and hard work. </p>
<p> <a href="http://money.cnn.com/2016/01/11/news/economy/rich-taxes/index.html?iid=EL"><span>Related: Rich are paying more in taxes but not as much as they used to</span></a> </p>
<p> The report also suggested the U.S. might not be the "jobs machine" it thinks it is, when compared to other countries. </p>
<p> It ranked near the bottom of the pack based on the levels of unemployment among men and women of prime working age. The study determined this by taking the ratio of employed men and women between the ages of 25 and 54 compared to the total population of each country. </p>
<p> The overall rankings of the countries were as follows:<span> <br />1. Finland <span> <br />2. Norway<span> <br />3. Australia <span> <br />4. Canada<span> <br />5. Germany<span> <br />6. France<span> <br />7. United Kingdom <span> <br />8. Italy<span> <br />9. Spain<span> <br />10. United States </span></span>
</span>
</span>
</span>
</span>
</span>
</span>
</span>
</span>
</p>
<p> The low ranking the U.S. received was due to its extreme levels of wealth and income inequality and the ineffectiveness of its "safety net" -- social programs aimed at reducing poverty. </p>
<p> <a href="http://money.cnn.com/2016/01/05/news/economy/chicago-segregated/index.html?iid=EL"><span>Related: Chicago is America's most segregated city</span></a> </p>
<p> The report concluded that the American safety net was ineffective because it provides only half the financial help people need. Additionally, the levels of assistance in the U.S. are generally lower than in other countries. </p>
<p><span> CNNMoney (New York) </span> <span>First published February 1, 2016: 1:28 AM ET</span> </p>
</div>
</div>

File diff suppressed because it is too large Load Diff

@ -1,8 +1,6 @@
{
"title": "Test script parsing",
"byline": null,
"dir": null,
"excerpt": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n tempor incididunt ut labore et dolore magna aliqua.",
"siteName": null,
"readerable": true
}

@ -1,11 +1,19 @@
<div id="readability-page-1" class="page">
<div>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.</p>
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
<div>
<p>Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</div>

@ -1,8 +0,0 @@
{
"title": "Daring Fireball: Colophon",
"byline": null,
"dir": null,
"excerpt": "Daring Fireball is written and produced by John Gruber.",
"siteName": null,
"readerable": true
}

@ -1,32 +0,0 @@
<div id="readability-page-1" class="page">
<div id="Box">
<p>Daring Fireball is written and produced by John Gruber.</p>
<p>
<a href="http://fakehost/graphics/author/addison-bw.jpg"> <img src="http://fakehost/graphics/author/addison-bw-425.jpg" alt="Photograph of the author." /></a>
<br /><em>Portrait by <a href="http://superbiate.com/inquiries/">George Del Barrio</a></em>
</p>
<h2>Mac Apps</h2>
<ul>
<li><a href="http://www.barebones.com/products/bbedit/">BBEdit</a></li>
<li><a href="http://www.flyingmeat.com/acorn/">Acorn</a></li>
<li><a href="http://www.red-sweater.com/marsedit/">MarsEdit</a></li>
<li><a href="http://aged-and-distilled.com/napkin/">Napkin</a></li>
<li><a href="http://www.barebones.com/products/Yojimbo/">Yojimbo</a></li>
<li><a href="http://www.panic.com/transmit/">Transmit</a></li>
<li><a href="http://latenightsw.com/sd4/index.html">Script Debugger</a></li>
<li><a href="http://www.ambrosiasw.com/utilities/snapzprox/">Snapz Pro X</a></li>
<li><a href="http://nightly.webkit.org/">WebKit</a></li>
</ul>
<h2>iPhone Apps</h2>
<ul>
<li><a href="http://vesperapp.co/">Vesper</a></li>
</ul>
<h2>Server Software</h2>
<p>The Daring Fireball website is hosted by <a href="http://joyent.com/">Joyent</a>.</p>
<p>Articles and links are published through <a href="http://movabletype.org/">Movable Type</a>. In addition to my own SmartyPants and Markdown plug-ins, Daring Fireball uses several excellent Movable Type plug-ins, including Brad Choates <a href="http://bradchoate.com/weblog/2003/06/24/regular-expressions">MT-Regex</a> and <a href="http://bradchoate.com/weblog/2004/10/20/mtifempty">MT-IfEmpty</a>, and <a href="http://bumppo.net/projects/amputator/">Nat Ironss Amputator</a>.</p>
<p>Stats are tracked using <a href="http://haveamint.com/">Mint</a>. Additional web nerdery, including the membership system, is fueled by <a href="http://perl.org/">Perl</a>, <a href="http://www.php.net/">PHP</a>, and <a href="http://www.mysql.com/">MySQL</a>.</p>
<h2>Web Standards</h2>
<p>Web standards are important, and Daring Fireball adheres to them. Specifically, Daring Fireballs HTML markup should validate as either <a href="http://www.whatwg.org/specs/web-apps/current-work/">HTML 5</a> or XHTML 4.01 Transitional, its layout is constructed using <a href="http://jigsaw.w3.org/css-validator/validator?uri=http://daringfireball.net/css/fireball_screen.css">valid CSS</a>, and its syndicated feed is <a href="http://feedvalidator.org/check?url=http%3A%2F%2Fdaringfireball.net%2Findex.xml">valid Atom</a>.</p>
<p>If Daring Fireball looks goofy in your browser, youre likely using a shitty browser that doesnt support web standards. Internet Explorer, Im looking in your direction. If you complain about this, I will laugh at you, because I do not care. If, however, you are using a modern, standards-compliant browser and have trouble viewing or reading Daring Fireball, please do let me know.</p>
</div>
</div>

@ -1,151 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Daring Fireball: Colophon</title>
<meta name="viewport" content="width=600, initial-scale=0.5, minimum-scale=0.45"/>
<link rel="apple-touch-icon-precomposed" href="/graphics/apple-touch-icon.png"/>
<link rel="shortcut icon" href="/graphics/favicon.ico?v=005"/>
<link rel="stylesheet" type="text/css" media="screen" href="/css/fireball_screen.css?v1.6"/>
<link rel="stylesheet" type="text/css" media="screen" href="/css/ie_sucks.php"/>
<link rel="stylesheet" type="text/css" media="print" href="/css/fireball_print.css?v01"/>
<link rel="alternate" type="application/atom+xml" href="/feeds/main"/>
<script src="/js/js-global/FancyZoom.js" type="text/javascript"></script>
<script src="/js/js-global/FancyZoomHTML.js" type="text/javascript"></script>
<link rel="shortcut icon" href="/favicon.ico"/> </head>
<body onload="setupZoom()">
<div id="Box">
<div id="Banner">
<a href="/" title="Daring Fireball: Home"><img src="/graphics/logos/" alt="Daring Fireball" height="56"/></a>
</div>
<div id="Sidebar">
<p>By <strong>John&nbsp;Gruber</strong></p>
<ul>
<!--&#9733;-->
<li><a href="/archive/" title="Previous articles.">Archive</a></li>
<li>
<script type="text/javascript">
// <![CDATA[
function ReadCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
var display_linked_list = ReadCookie('displayLinkedList');
var li_linked = '<a href="/linked/" title="The Linked List.">Linked List<\/a>';
if (display_linked_list == "hide") {
// Linked List is off on home page, so show it in menu:
document.write(li_linked + "<\/li>\n<li>");
} else {
// Default to not putting separate LL item in sidebar:
}
// ]]>
</script>
</li>
<li><a href="/thetalkshow/" title="The worlds most popular podcast.">The Talk Show</a></li>
<li><a href="http://vesperapp.co/" title="A simple, elegant iOS app for collecting notes, ideas, thoughts, and images.">Vesper: <em>Now for iPad</em></a></li>
<li><a href="/projects/" title="Software projects, including SmartyPants and Markdown.">Projects</a></li>
<li><a href="/contact/" title="How to send email regarding Daring Fireball.">Contact</a></li>
<li><a href="/colophon/" title="About this site and the tools used to produce it.">Colophon</a></li>
<li><a href="/feeds/">RSS Feed</a></li>
<li><a href="https://twitter.com/daringfireball">Twitter</a></li>
<li><a href="/feeds/sponsors/">Sponsorship</a></li>
</ul>
</div>
<!-- Sidebar -->
<div id="Main">
<div class="article">
<h1>About This Site</h1>
<p>Daring Fireball is written and produced by John Gruber.</p>
<p>
<a href="/graphics/author/addison-bw.jpg"> <img src="/graphics/author/addison-bw-425.jpg" alt="Photograph of the author." style="border: 0;"/></a>
<br/><em>Portrait by <a href="http://superbiate.com/inquiries/">George Del Barrio</a></em> </p>
<h2>Mac Apps</h2>
<ul>
<li><a href="http://www.barebones.com/products/bbedit/">BBEdit</a></li>
<li><a href="http://www.flyingmeat.com/acorn/">Acorn</a></li>
<li><a href="http://www.red-sweater.com/marsedit/">MarsEdit</a></li>
<li><a href="http://aged-and-distilled.com/napkin/">Napkin</a></li>
<li><a href="http://www.barebones.com/products/Yojimbo/">Yojimbo</a></li>
<li><a href="http://www.panic.com/transmit/">Transmit</a></li>
<li><a href="http://latenightsw.com/sd4/index.html">Script Debugger</a></li>
<li><a href="http://www.ambrosiasw.com/utilities/snapzprox/">Snapz Pro X</a></li>
<li><a href="http://nightly.webkit.org/">WebKit</a></li>
</ul>
<h2>iPhone Apps</h2>
<ul>
<li><a href="http://vesperapp.co/">Vesper</a></li>
</ul>
<h2>Server Software</h2>
<p>The Daring Fireball website is hosted by <a href="http://joyent.com/">Joyent</a>.</p>
<p>Articles and links are published through <a href="http://movabletype.org/">Movable Type</a>. In addition to my own SmartyPants and Markdown plug-ins, Daring Fireball uses several excellent Movable Type plug-ins, including Brad Choates <a href="http://bradchoate.com/weblog/2003/06/24/regular-expressions">MT-Regex</a> and <a href="http://bradchoate.com/weblog/2004/10/20/mtifempty">MT-IfEmpty</a>, and <a href="http://bumppo.net/projects/amputator/">Nat Ironss Amputator</a>.</p>
<p>Stats are tracked using <a href="http://haveamint.com/">Mint</a>. Additional web nerdery, including the membership system, is fueled by <a href="http://perl.org/">Perl</a>, <a href="http://www.php.net/">PHP</a>, and <a href="http://www.mysql.com/">MySQL</a>.</p>
<h2>Web Standards</h2>
<p>Web standards are important, and Daring Fireball adheres to them. Specifically, Daring Fireballs HTML markup should validate as either <a href="http://www.whatwg.org/specs/web-apps/current-work/">HTML 5</a> or XHTML 4.01 Transitional, its layout is constructed using <a href="http://jigsaw.w3.org/css-validator/validator?uri=http://daringfireball.net/css/fireball_screen.css">valid CSS</a>, and its syndicated feed is <a href="http://feedvalidator.org/check?url=http%3A%2F%2Fdaringfireball.net%2Findex.xml">valid Atom</a>.</p>
<p>If Daring Fireball looks goofy in your browser, youre likely using a shitty browser that doesnt support web standards. Internet Explorer, Im looking in your direction. If you complain about this, I will laugh at you, because I do not care. If, however, you are using a modern, standards-compliant browser and have trouble viewing or reading Daring Fireball, please do let me know.</p>
</div>
<!-- article -->
<div id="Footer">
<form id="SiteSearch" action="https://daringfireball.net/search" method="get" style="margin-bottom: 2.5em;">
<div>
<input name="q" type="text" value="" style="margin-right: 8px; width: 66%;"/>
<input type="submit" value="Search"/> </div>
</form>
<p class="smallprint"> <a href="/preferences/" title="Customize the font size and presentation options for this web site.">Display Preferences</a>
<br/>
<br/> Copyright © 20022015 The Daring Fireball Company LLC. </p>
</div>
<div id="SidebarTheDeck">
<script type="text/javascript">
// <![CDATA[
(function(id) {
document.write('<script type="text/javascript" src="' + '//connect.decknetwork.net/deck' + id + '_js.php?' + (new Date().getTime()) + '"></' + 'script>');
})("DF");
var deckDiv = document.getElementById("SidebarTheDeck");
var sidebarDiv = document.getElementById("Sidebar");
sidebarDiv.appendChild(deckDiv);
// ]]>
</script>
<p id="ViaTheDeck">
<a href="http://decknetwork.net/"> <img src="//daringfireball.net/graphics/madison/via_the_deck.png" alt="Ads via The Deck" class="the_deck_promo" width="70"/> <span>Ads via The Deck</span> </a>
</p>
</div>
<!-- Google Analytics -->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-593949-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script>
<!-- Asynchronously load Mint -->
<script type="text/javascript">
(function() {
var ma = document.createElement('script');
ma.type = 'text/javascript';
ma.src = '/mint/?js';
ma.async = true;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ma, s);
})();
</script>
</div>
<!-- Main -->
</div>
<!-- box -->
</body>
</html>

@ -1,8 +0,0 @@
{
"title": "Document",
"byline": null,
"dir": null,
"excerpt": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Natus eaque totam provident obcaecati nisi praesentium iusto velit fuga debitis quidem ut repellat corrupti, eligendi inventore quibusdam perspiciatis delectus omnis pariatur excepturi quasi fugit? A adipisci natus nostrum, qui aperiam, at culpa corrupti autem enim earum vitae. Nostrum et officiis facere ex recusandae tenetur, delectus odit provident soluta id perferendis ducimus quibusdam corporis rerum voluptatem architecto sequi beatae quod mollitia voluptatibus earum tempora inventore ut. Deserunt reprehenderit recusandae nostrum, eaque fuga cum, repellat, perspiciatis ducimus in non consequatur ratione. Sint rerum necessitatibus deleniti odio earum voluptatum eos modi ab dolor minus.",
"siteName": null,
"readerable": true
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,8 +0,0 @@
{
"title": "On Recent Controversial Events - Bradley M. Kuhn ( Brad ) ( bkuhn )",
"byline": "Bradley M. Kuhn (http://ebb.org/bkuhn/)",
"dir": null,
"excerpt": "The website of Bradley M. Kuhn, aka Brad, aka bkuhn. This site includes his GPG keys, resume, blog, projects list, software, interviews, speeches and writing.",
"siteName": null,
"readerable": true
}

@ -1,26 +0,0 @@
<div id="readability-page-1" class="page">
<div id="contentWithSidebar">
<p> Tuesday 15 October 2019 by Bradley M. Kuhn </p>
<p> The last 33 days have been unprecedentedly difficult for the software freedom community and for me personally. Folks have been emailing, phoning, texting, tagging me on social media (— the last of which has been funny, because all my social media accounts are placeholder accounts). But, just about everyone has urged me to comment on the serious issues that the software freedom community now faces. Until now, I have stayed silent regarding all these current topics: from Richard M. Stallman (RMS)'s public statements, to <a href="https://www.fsf.org/news/richard-m-stallman-resigns">his resignation from the Free Software Foundation (FSF)</a>, to the Epstein scandal and its connection to MIT. I've also avoided generally commenting on software freedom organizational governance during this period. I did this for good reason, which is explained below. However, in this blog post, I now share my primary comments on the matters that seem to currently be of the utmost attention of the Open Source and Free Software communities. </p>
<p> I have been silent the last month because, until two days ago, I was an at-large member of <a href="https://www.fsf.org/about/staff-and-board">FSF's Board of Directors</a>, and a <a href="https://static.fsf.org/nosvn/fsf-amended-bylaws-current.pdf">Voting Member</a> of the FSF. As a member of FSF's two leadership bodies, I was abiding by a reasonable request from the FSF management and my duty to the organization. Specifically, the FSF asked that all communication during the crisis <a href="https://www.fsf.org/news/richard-m-stallman-resigns">come</a> <a href="https://www.fsf.org/news/fsf-and-gnu">directly</a> from FSF officers and not from at-large directors and/or Voting Members. Furthermore, the FSF management asked all Directors and Voting Members to remain silent on this entire matter — even on issues only tangentially related to the current situation, and even when speaking in our own capacity (e.g., on our own blogs like this one). The FSF is an important organization, and I take any request from the FSF seriously — so I abided fully with their request. </p>
<p> The situation was further complicated because folks at my employer, Software Freedom Conservancy (where I also serve on the <a href="https://sfconservancy.org/about/board/#bkuhn">Board of Directors</a>) had strong opinions about this matter as well. Fortunately, the FSF and Conservancy both had already created clear protocols for what I should do if ever there was a disagreement or divergence of views between Conservancy and FSF. I therefore was recused fully from the planning, drafting, and timing of Conservancy's statement on this matter. I thank my colleagues at the Conservancy for working so carefully to keep me entirely outside the loop on their statement and to diligently assure that it was straight-forward for me to manage any potential organizational disagreements. I also thank those at the FSF who outlined clear protocols (ahead of time, back in March 2019) in case a situation like this ever came up. I also know my colleagues at Conservancy care deeply, as I do, about the health and welfare of the FSF and its mission of fighting for universal software freedom for all. None of us want, nor have, any substantive disagreement over software freedom issues. </p>
<p> I take very seriously my duty to the various organizations where I have (or have had) affiliations. More generally, I champion non-profit organizational transparency. Unfortunately, the current crisis left me in a quandary between the overarching goal of community transparency and abiding by FSF management's directives. Now that I've left the FSF Board of Directors, FSF's Voting Membership, and all my FSF volunteer roles (which ends my 22-year uninterrupted affiliation with the FSF), I can now comment on the substantive issues that face not just the FSF, but the Free Software community as a whole, while continuing to adhere to my past duty of acting in FSF's best interest. In other words, my affiliation with the FSF has come to an end for many good and useful reasons. The end to this affiliation allows me to speak directly about the core issues at the heart of the community's current crisis. </p>
<p> Firstly, all these events — from RMS' public comments on the MIT mailing list, to RMS' resignation from the FSF to RMS' discussions about the next steps for the GNU project — <em>seem</em> to many to have happened ridiculously quickly. But it wasn't actually fast at all. In fact, these events were culmination of issues that were slowly growing in concern to many people, including me. </p>
<p> For the last two years, I had been a loud internal voice in the FSF leadership regarding RMS' Free-Software-unrelated public statements; I felt strongly that it was in the best interest of the FSF to actively seek to limit such statements, and that it was my duty to FSF to speak out about this within the organization. Those who only learned of this story in the last month (understandably) believed <a href="https://medium.com/@selamjie/remove-richard-stallman-fec6ec210794">Selam G.'s Medium post</a> raised an entirely new issue. <a href="https://web.archive.org/web/20161107050933/https://www.stallman.org/archives/2016-jul-oct.html#31_October_2016_(Down&apos;s_syndrome)">In</a> <a href="https://web.archive.org/web/20170202025227/https://www.stallman.org/archives/2016-nov-feb.html#14_December_2016_(Campaign_of_bull-headed_prudery)">fact</a>, <a href="https://web.archive.org/web/20170224174306/https://www.stallman.org/archives/2016-nov-feb.html#23_February_2017_(A_violent_sex_offender)">RMS'</a> <a href="https://web.archive.org/web/20170612074722/http://stallman.org/archives/2017-mar-jun.html#26_May_2017_(Prudish_ignorantism)">views</a> <a href="https://web.archive.org/web/20170616044924/https://www.stallman.org/archives/2017-mar-jun.html#13_June_2017_(Sex_offender_registry)">and</a> <a href="https://web.archive.org/web/20171020041022/http://stallman.org/archives/2017-jul-oct.html#10_October_2017_(Laws_against_having_sex_with_an_animal)">statements</a> <a href="https://web.archive.org/web/20180131020215/https://stallman.org/archives/2017-jul-oct.html#29_October_2017_(Pestering_women)">posted</a> <a href="https://web.archive.org/web/20180104112431/https://www.stallman.org/archives/2017-nov-feb.html#27_November_2017_(Roy_Moore&apos;s_relationships)">on</a> <a href="https://web.archive.org/web/20180509120046/https://stallman.org/archives/2018-mar-jun.html#30_April_2018_(UN_peacekeepers_in_South_Sudan)">stallman.org</a> <a href="https://web.archive.org/web/20180911075211/https://www.stallman.org/archives/2018-jul-oct.html#17_July_2018_(The_bullshitter&apos;s_flirting)">about</a> <a href="https://web.archive.org/web/20180911075211/https://www.stallman.org/archives/2018-jul-oct.html#21_August_2018_(Age_and_attraction)">sexual</a> <a href="https://web.archive.org/web/20180924231708/https://stallman.org/archives/2018-jul-oct.html#23_September_2018_(Cody_Wilson)">morality</a> <a href="https://web.archive.org/web/20180919100154/https://stallman.org/antiglossary.html#assult">escalated</a> <a href="https://web.archive.org/web/20181113161736/https://www.stallman.org/archives/2018-sep-dec.html#6_November_2018_(Sex_according_to_porn)">for</a> <a href="https://web.archive.org/web/20190325024048/https://stallman.org/archives/2019-jan-apr.html#14_February_2019_(Respecting_peoples_right_to_say_no)">the</a> <a href="https://www.stallman.org/archives/2019-may-aug.html#11_June_2019_(Stretching_meaning_of_terms)">worse</a> <a href="https://web.archive.org/web/20190801201704/https://stallman.org/archives/2019-may-aug.html#12_June_2019_(Declining_sex_rates)">over</a> <a href="https://web.archive.org/web/20190801201704/https://stallman.org/archives/2019-may-aug.html#30_July_2019_(Al_Franken)">the</a> <a href="https://web.archive.org/web/20190903050208/https://stallman.org/archives/2019-jul-oct.html#27_August_2019_(Me-too_frenzy)">last</a> <a href="https://web.archive.org/web/20191011023557/https://stallman.org/archives/2019-jul-oct.html#21_September_2019_(Sex_workers)">few</a> <a href="https://web.archive.org/web/20180924231708/https://stallman.org/archives/2018-jul-oct.html#23_September_2018_(Cody_Wilson)">years</a>. When the escalation started, I still considered RMS both a friend and colleague, and I attempted to argue with him at length to convince him that some of his positions were harmful to sexual assault survivors and those who are sex trafficked, and to the people who devote their lives in service to such individuals. More importantly to the FSF, I attempted to persuade RMS that launching a controversial campaign on sexual behavior and morality was counter to his and FSF's mission to advance software freedom, and told RMS that my duty as an FSF Director was to assure the best outcome for the FSF, which <acronym title="in my opinion">IMO</acronym> didn't include having a leader who made such statements. Not only is human sexual behavior not a topic on which RMS has adequate academic expertise, but also his positions appear to ignore significant research and widely available information on the subject. Many of his comments, while occasionally politically intriguing, lack empathy for people who experienced trauma. </p>
<p> IMO, this is not and has never been a Free Speech issue. I do believe freedom of speech links directly to software freedom: indeed, I see the freedom to publish software under Free licenses as almost a corollary to the freedom of speech. However, we do not need to follow leadership from those whose views we fundamentally disagree. Moreover, organizations need not and should not elevate spokespeople and leaders who speak regularly on unrelated issues that organizations find do not advance their mission, and/or that alienate important constituents. I, like many other software freedom leaders, curtail my public comments on issues not related to <acronym title="Free and Open Source Software">FOSS</acronym>. (Indeed, I would not even be commenting on <em>this issue</em> if it had not become a central issue of concern to the software freedom community.) Leaders have power, and they must exercise the power of their words with <a href="https://lwn.net/Articles/770966/">restraint, not with impunity</a>. </p>
<p> RMS has consistently argued that there was a campaign of “prudish intimidation” — seeking to keep him quiet about his views on sexuality. After years of conversing with RMS about how his non-software-freedom views were a distraction, an indulgence, and downright problematic, his general response was to make even more public comments of this nature. The issue is not about RMS' right to say what he believes, nor is it even about whether or not you agree or disagree with RMS' statements. The question is whether an organization should have a designated leader who is on a sustained, public campaign advocating about an unrelated issue that many consider controversial. It really doesn't matter what your view about the controversial issue is; a leader who refuses to stop talking loudly about unrelated issues eventually creates an untenable distraction from the radical activism you're actively trying to advance. The message of universal software freedom is a radical cause; it's basically impossible for one individual to effectively push forward two unrelated controversial agendas at once. In short, the radical message of software freedom became overshadowed by RMS' radical views about sexual morality. </p>
<p> And here is where I say the thing that may infuriate many but it's what I believe: I think RMS took a useful step by resigning some of his leadership roles at the FSF. I thank RMS for taking that step, and I wish the FSF Directors well in their efforts to assure that the FSF becomes a welcoming organization to all who care about universal software freedom. The <a href="https://www.fsf.org/about/">FSF's mission</a> is essential to our technological future, and we should all support that mission. I care deeply about that mission myself and have worked and will continue to work in our community in the best interest of the mission. </p>
<p> I'm admittedly struggling to find a way to work again with RMS, given his views on sexual morality and his behaviors stemming from those views. I explicitly do not agree with <a href="https://web.archive.org/web/20180919100154/https://stallman.org/antiglossary.html#assult">this “(re-)definition” of sexual assault</a>. Furthermore, I believe uninformed statements about sexual assault are irresponsible and cause harm to victims. #MeToo is <strong><a href="https://web.archive.org/web/20190903050208/https://stallman.org/archives/2019-jul-oct.html#27_August_2019_(Me-too_frenzy)">not a “frenzy”</a></strong>; it is a global movement by individuals who have been harmed seeking to hold both bad actors <em>and</em> society-at-large accountable for ignoring systemic wrongs. Nevertheless, I still am proud of the <a href="https://www.gnu.org/philosophy/freedom-or-power.en.html">essay that I co-wrote with RMS</a> and still find <a href="https://www.gnu.org/gnu/manifesto.en.html">many</a> <a href="https://www.gnu.org/philosophy/free-sw.html">of</a> <a href="https://www.gnu.org/philosophy/why-free.html">RMS'</a> <a href="https://www.gnu.org/philosophy/pragmatic.html">other</a> <a href="https://www.gnu.org/philosophy/microsoft-old.html">essays</a> <a href="https://www.gnu.org/philosophy/gpl-american-way.html">compelling</a>, <a href="https://www.gnu.org/licenses/why-not-lgpl.html">important</a>, <a href="https://www.gnu.org/philosophy/stallman-kth.en.html">and</a> <a href="https://www.gnu.org/philosophy/who-does-that-server-really-serve.en.html">relevant</a>. </p>
<p> I want the FSF to succeed in its mission and enter a new era of accomplishments. I've spent the last 22 years, without a break, dedicating substantial time, effort, care and loyalty to the various FSF roles that I've had: including employee, volunteer, at-large Director, and Voting Member. Even though my duties to the FSF are done, and my relationship with the FSF is no longer formal, I still think the FSF is a valuable institution worth helping and saving, specifically because the FSF was founded for a mission that I deeply support. And we should also realize that RMS — a human being (who is flawed like the rest of us) — invented that mission. </p>
<p> As culture change becomes more rapid, I hope we can find reasonable nuance and moderation on our complex analysis about people and their disparate views, while we also hold individuals fully accountable for their actions. That's the difficulty we face in the post-post-modern culture of the early twenty-first century. Most importantly, I believe we must find a way to stand firm for software freedom while also making a safe environment for victims of sexual assault, sexual abuse, gaslighting, and other deplorable actions. </p>
<p> Posted on Tuesday 15 October 2019 at 09:11 by Bradley M. Kuhn. </p>
</div>
<p>
<code>#include &lt;std/disclaimer.h&gt;</code><br />
<code>use Standard::Disclaimer;</code><br />
<code>from standard import disclaimer</code><br />
<code>SELECT full_text FROM standard WHERE type = 'disclaimer';</code>
</p>
<p> Both previously and presently, I have been employed by and/or done work for various organizations that also have views on Free, Libre, and Open Source Software. As should be blatantly obvious, this is my website, not theirs, so please do not assume views and opinions here belong to any such organization. Since I do co-own ebb.org with my wife, it may not be so obvious that these aren't her views and opinions, either. </p>
<p> ebb <sup></sup> is a service mark of Bradley M. Kuhn. </p>
</div>

@ -1,485 +0,0 @@
<!DOCTYPE html>
<html lang="en-US" xml:lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>
On Recent Controversial Events - Bradley M. Kuhn ( Brad ) ( bkuhn )
</title>
<meta content="Bradley M. Kuhn (http://ebb.org/bkuhn/)" name="author" />
<link href="http://ebb.org/bkuhn/rss.xml" rel="alternate" title="Whole Website RSS for Bradley M. Kuhn" type="application/rss+xml" />
<link href="http://ebb.org/bkuhn/blog/rss.xml" rel="alternate" title="Blog RSS for Bradley M. Kuhn" type="application/rss+xml" />
<link href="http://ebb.org/bkuhn/blog/rss.xml" rel="alternate" title="Articles / Interviews RSS for Bradley M. Kuhn" type="application/rss+xml" />
<link href="/css/screen.css" rel="stylesheet" type="text/css" />
<link href="mailto:bkuhn@ebb.org" rel="made" />
<meta content="kuhn, brad, bradley, bkuhn, hacker, gnu, free, software, linux, unix, perl, liberal, pgp, gpg, geek, system, administrator, network, free software, developer, new york, NY, computer, open source, hacker, software, freedom, software freedom" http-equiv="Keywords" name="Keywords" />
<meta content="The website of Bradley M. Kuhn, aka Brad, aka bkuhn. This site includes his GPG keys, resume, blog, projects list, software, interviews, speeches and writing." http-equiv="Description" name="Description" />
</head>
<body>
<div id="site">
<div id="sidebar">
<div id="title">
<p>
<a href="/bkuhn">Bradley M. Kuhn</a> <a href="http://ebb.org/bkuhn/rss.xml"><img alt="[RSS of Whole Site]" border="0" src="/images/feed-icon-14x14.png" /></a>
</p>
</div>
<div id="menu">
<ul>
<li>
<a href="/bkuhn/contact">Contact</a>
</li>
<li>
<a href="/bkuhn/blog">Blog</a> &#160; <a href="http://ebb.org/bkuhn/blog/rss.xml"><img alt="[RSS of Blog]" border="0" src="/images/feed-icon-14x14.png" /></a>
</li>
<li>
<a href="http://identi.ca/bkuhn/">Pump.io Social Network</a>
</li><!-- %li= link_to("About", "/bkuhn/about") -->
<!-- %li= link_to("Speeches / Talks", "/bkuhn/speeches/") -->
<li>
<a href="/bkuhn/articles">Interviews / Articles</a> &#160; <a href="http://ebb.org/bkuhn/articles/rss.xml"><img alt="[RSS of Articles]" border="0" src="/images/feed-icon-14x14.png" /></a>
</li>
<li>
<a href="/bkuhn/code/">Software</a>
</li>
<li>
<a href="/bkuhn/resume">Résumé</a>
</li>
</ul>
</div>
<h2>
<a href="/bkuhn/tags">Tag</a> Cloud
</h2>
<ol id="tag-cloud">
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#accounting">accounting</a>
</li>
<li class="tier-4" title="24 posts">
<a href="/bkuhn/tags.html#advocacy">advocacy</a>
</li>
<li class="tier-4" title="16 posts">
<a href="/bkuhn/tags.html#agpl">agpl</a>
</li>
<li class="tier-3" title="6 posts">
<a href="/bkuhn/tags.html#android">android</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#apache">apache</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#apple">apple</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#apt">apt</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#artistic">artistic</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#asterisk">asterisk</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#automotive">automotive</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#autonomous">autonomous</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#award">award</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#bilski">bilski</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#canonical">canonical</a>
</li>
<li class="tier-3" title="7 posts">
<a href="/bkuhn/tags.html#cla">cla</a>
</li>
<li class="tier-5" title="39 posts">
<a href="/bkuhn/tags.html#community">community</a>
</li>
<li class="tier-3" title="6 posts">
<a href="/bkuhn/tags.html#compliance">compliance</a>
</li>
<li class="tier-4" title="15 posts">
<a href="/bkuhn/tags.html#conferences">conferences</a>
</li>
<li class="tier-5" title="37 posts">
<a href="/bkuhn/tags.html#conservancy">conservancy</a>
</li>
<li class="tier-3" title="8 posts">
<a href="/bkuhn/tags.html#copyleft">copyleft</a>
</li>
<li class="tier-5" title="56 posts">
<a href="/bkuhn/tags.html#copyright">copyright</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#cow-orking">cow-orking</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#cpp">cpp</a>
</li>
<li class="tier-3" title="9 posts">
<a href="/bkuhn/tags.html#debian">debian</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#denounce">denounce</a>
</li>
<li class="tier-2" title="4 posts">
<a href="/bkuhn/tags.html#development">development</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#diversity">diversity</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#emacs">emacs</a>
</li>
<li class="tier-2" title="4 posts">
<a href="/bkuhn/tags.html#encryption">encryption</a>
</li>
<li class="tier-3" title="10 posts">
<a href="/bkuhn/tags.html#enforcement">enforcement</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#exceptions">exceptions</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#faif">faif</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#fdl">fdl</a>
</li>
<li class="tier-4" title="11 posts">
<a href="/bkuhn/tags.html#for-profit">for-profit</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#fosdem">fosdem</a>
</li>
<li class="tier-4" title="13 posts">
<a href="/bkuhn/tags.html#fsf">fsf</a>
</li>
<li class="tier-2" title="4 posts">
<a href="/bkuhn/tags.html#gcc">gcc</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#git">git</a>
</li>
<li class="tier-4" title="13 posts">
<a href="/bkuhn/tags.html#gnome">gnome</a>
</li>
<li class="tier-3" title="6 posts">
<a href="/bkuhn/tags.html#gnu">gnu</a>
</li>
<li class="tier-2" title="4 posts">
<a href="/bkuhn/tags.html#google">google</a>
</li>
<li class="tier-6" title="107 posts">
<a href="/bkuhn/tags.html#gpl">gpl</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#gpl-compatibility">gpl-compatibility</a>
</li>
<li class="tier-5" title="47 posts">
<a href="/bkuhn/tags.html#gpl-enforcement">gpl-enforcement</a>
</li>
<li class="tier-2" title="4 posts">
<a href="/bkuhn/tags.html#gplv3">gplv3</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#guadec">guadec</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#identica">identica</a>
</li>
<li class="tier-4" title="17 posts">
<a href="/bkuhn/tags.html#infringement">infringement</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#java">java</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#javascript">javascript</a>
</li>
<li class="tier-2" title="4 posts">
<a href="/bkuhn/tags.html#jvm">jvm</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#launchpad">launchpad</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#ldap">ldap</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#lgpl">lgpl</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#libreoffice">libreoffice</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#libreplanet">libreplanet</a>
</li>
<li class="tier-6" title="72 posts">
<a href="/bkuhn/tags.html#licensing">licensing</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#lindows">lindows</a>
</li>
<li class="tier-4" title="11 posts">
<a href="/bkuhn/tags.html#linux">linux</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#maemo">maemo</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#mail">mail</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#meego">meego</a>
</li>
<li class="tier-4" title="17 posts">
<a href="/bkuhn/tags.html#microsoft">microsoft</a>
</li>
<li class="tier-3" title="6 posts">
<a href="/bkuhn/tags.html#mobile">mobile</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#moblin">moblin</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#mono">mono</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#motorola">motorola</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#mta">mta</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#mysql">mysql</a>
</li>
<li class="tier-4" title="13 posts">
<a href="/bkuhn/tags.html#net-services">net-services</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#nlp">nlp</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#nokia">nokia</a>
</li>
<li class="tier-4" title="23 posts">
<a href="/bkuhn/tags.html#non-profit">non-profit</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#np-complete">np-complete</a>
</li>
<li class="tier-3" title="7 posts">
<a href="/bkuhn/tags.html#open-core">open-core</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#open-foam">open-foam</a>
</li>
<li class="tier-2" title="4 posts">
<a href="/bkuhn/tags.html#oracle">oracle</a>
</li>
<li class="tier-2" title="4 posts">
<a href="/bkuhn/tags.html#parrot">parrot</a>
</li>
<li class="tier-4" title="16 posts">
<a href="/bkuhn/tags.html#patents">patents</a>
</li>
<li class="tier-3" title="6 posts">
<a href="/bkuhn/tags.html#perl">perl</a>
</li>
<li class="tier-3" title="6 posts">
<a href="/bkuhn/tags.html#perljvm">perljvm</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#permissive-license">permissive-license</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#piracy">piracy</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#podcast">podcast</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#podjango">podjango</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#poker">poker</a>
</li>
<li class="tier-4" title="14 posts">
<a href="/bkuhn/tags.html#politics">politics</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#postfix">postfix</a>
</li>
<li class="tier-4" title="13 posts">
<a href="/bkuhn/tags.html#proprietary">proprietary</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#qt">qt</a>
</li>
<li class="tier-2" title="4 posts">
<a href="/bkuhn/tags.html#replicant">replicant</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#requiem">requiem</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#rtlinux">rtlinux</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#scale">SCALE</a>
</li>
<li class="tier-3" title="6 posts">
<a href="/bkuhn/tags.html#sco">sco</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#scotus">scotus</a>
</li>
<li class="tier-3" title="5 posts">
<a href="/bkuhn/tags.html#security">security</a>
</li>
<li class="tier-3" title="6 posts">
<a href="/bkuhn/tags.html#sexism">sexism</a>
</li>
<li class="tier-2" title="4 posts">
<a href="/bkuhn/tags.html#sflc">sflc</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#slicing">slicing</a>
</li>
<li class="tier-3" title="7 posts">
<a href="/bkuhn/tags.html#social-justice">social-justice</a>
</li>
<li class="tier-3" title="6 posts">
<a href="/bkuhn/tags.html#software">software</a>
</li>
<li class="tier-6" title="108 posts">
<a href="/bkuhn/tags.html#software-freedom">software-freedom</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#speeches">speeches</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#stet">stet</a>
</li>
<li class="tier-2" title="4 posts">
<a href="/bkuhn/tags.html#talks">talks</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#tcl">tcl</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#teaching">teaching</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#tech-press">tech-press</a>
</li>
<li class="tier-5" title="35 posts">
<a href="/bkuhn/tags.html#technology">technology</a>
</li>
<li class="tier-2" title="2 posts">
<a href="/bkuhn/tags.html#thesis">thesis</a>
</li>
<li class="tier-3" title="5 posts">
<a href="/bkuhn/tags.html#trademarks">trademarks</a>
</li>
<li class="tier-3" title="10 posts">
<a href="/bkuhn/tags.html#ubuntu">ubuntu</a>
</li>
<li class="tier-1" title="1 post">
<a href="/bkuhn/tags.html#voip">voip</a>
</li>
<li class="tier-2" title="3 posts">
<a href="/bkuhn/tags.html#xen">xen</a>
</li>
</ol>
<h2>
Powered by
</h2><a href="https://gitorious.org/bkuhn/jekyll/source/HEAD:">A Very Old Fork of Jekyll</a> <a href="https://gitorious.org/bkuhn/website/source/HEAD:">"Source Code" for this site</a>
</div>
<div id="contentWithSidebar">
<div id="post">
<h1>
On Recent Controversial Events
</h1>
<p class="topAttributionWithDate">
Tuesday 15 October 2019 by Bradley M. Kuhn
</p>
<p>
The last 33 days have been unprecedentedly difficult for the software freedom community and for me personally. Folks have been emailing, phoning, texting, tagging me on social media (— the last of which has been funny, because all my social media accounts are placeholder accounts). But, just about everyone has urged me to comment on the serious issues that the software freedom community now faces. Until now, I have stayed silent regarding all these current topics: from Richard M. Stallman (RMS)'s public statements, to <a href="https://www.fsf.org/news/richard-m-stallman-resigns">his resignation from the Free Software Foundation (FSF)</a>, to the Epstein scandal and its connection to MIT. I've also avoided generally commenting on software freedom organizational governance during this period. I did this for good reason, which is explained below. However, in this blog post, I now share my primary comments on the matters that seem to currently be of the utmost attention of the Open Source and Free Software communities.
</p>
<p>
I have been silent the last month because, until two days ago, I was an at-large member of <a href="https://www.fsf.org/about/staff-and-board">FSF's Board of Directors</a>, and a <a href="https://static.fsf.org/nosvn/fsf-amended-bylaws-current.pdf">Voting Member</a> of the FSF. As a member of FSF's two leadership bodies, I was abiding by a reasonable request from the FSF management and my duty to the organization. Specifically, the FSF asked that all communication during the crisis <a href="https://www.fsf.org/news/richard-m-stallman-resigns">come</a> <a href="https://www.fsf.org/news/fsf-and-gnu">directly</a> from FSF officers and not from at-large directors and/or Voting Members. Furthermore, the FSF management asked all Directors and Voting Members to remain silent on this entire matter — even on issues only tangentially related to the current situation, and even when speaking in our own capacity (e.g., on our own blogs like this one). The FSF is an important organization, and I take any request from the FSF seriously — so I abided fully with their request.
</p>
<p>
The situation was further complicated because folks at my employer, Software Freedom Conservancy (where I also serve on the <a href="https://sfconservancy.org/about/board/#bkuhn">Board of Directors</a>) had strong opinions about this matter as well. Fortunately, the FSF and Conservancy both had already created clear protocols for what I should do if ever there was a disagreement or divergence of views between Conservancy and FSF. I therefore was recused fully from the planning, drafting, and timing of Conservancy's statement on this matter. I thank my colleagues at the Conservancy for working so carefully to keep me entirely outside the loop on their statement and to diligently assure that it was straight-forward for me to manage any potential organizational disagreements. I also thank those at the FSF who outlined clear protocols (ahead of time, back in March 2019) in case a situation like this ever came up. I also know my colleagues at Conservancy care deeply, as I do, about the health and welfare of the FSF and its mission of fighting for universal software freedom for all. None of us want, nor have, any substantive disagreement over software freedom issues.
</p>
<p>
I take very seriously my duty to the various organizations where I have (or have had) affiliations. More generally, I champion non-profit organizational transparency. Unfortunately, the current crisis left me in a quandary between the overarching goal of community transparency and abiding by FSF management's directives. Now that I've left the FSF Board of Directors, FSF's Voting Membership, and all my FSF volunteer roles (which ends my 22-year uninterrupted affiliation with the FSF), I can now comment on the substantive issues that face not just the FSF, but the Free Software community as a whole, while continuing to adhere to my past duty of acting in FSF's best interest. In other words, my affiliation with the FSF has come to an end for many good and useful reasons. The end to this affiliation allows me to speak directly about the core issues at the heart of the community's current crisis.
</p>
<p>
Firstly, all these events — from RMS' public comments on the MIT mailing list, to RMS' resignation from the FSF to RMS' discussions about the next steps for the GNU project — <em>seem</em> to many to have happened ridiculously quickly. But it wasn't actually fast at all. In fact, these events were culmination of issues that were slowly growing in concern to many people, including me.
</p>
<p>
For the last two years, I had been a loud internal voice in the FSF leadership regarding RMS' Free-Software-unrelated public statements; I felt strongly that it was in the best interest of the FSF to actively seek to limit such statements, and that it was my duty to FSF to speak out about this within the organization. Those who only learned of this story in the last month (understandably) believed <a href="https://medium.com/@selamjie/remove-richard-stallman-fec6ec210794">Selam G.'s Medium post</a> raised an entirely new issue. <a href="https://web.archive.org/web/20161107050933/https://www.stallman.org/archives/2016-jul-oct.html#31_October_2016_(Down's_syndrome)">In</a> <a href="https://web.archive.org/web/20170202025227/https://www.stallman.org/archives/2016-nov-feb.html#14_December_2016_(Campaign_of_bull-headed_prudery)">fact</a>, <a href="https://web.archive.org/web/20170224174306/https://www.stallman.org/archives/2016-nov-feb.html#23_February_2017_(A_violent_sex_offender)">RMS'</a> <a href="https://web.archive.org/web/20170612074722/http://stallman.org/archives/2017-mar-jun.html#26_May_2017_(Prudish_ignorantism)">views</a> <a href="https://web.archive.org/web/20170616044924/https://www.stallman.org/archives/2017-mar-jun.html#13_June_2017_(Sex_offender_registry)">and</a> <a href="https://web.archive.org/web/20171020041022/http://stallman.org/archives/2017-jul-oct.html#10_October_2017_(Laws_against_having_sex_with_an_animal)">statements</a> <a href="https://web.archive.org/web/20180131020215/https://stallman.org/archives/2017-jul-oct.html#29_October_2017_(Pestering_women)">posted</a> <a href="https://web.archive.org/web/20180104112431/https://www.stallman.org/archives/2017-nov-feb.html#27_November_2017_(Roy_Moore's_relationships)">on</a> <a href="https://web.archive.org/web/20180509120046/https://stallman.org/archives/2018-mar-jun.html#30_April_2018_(UN_peacekeepers_in_South_Sudan)">stallman.org</a> <a href="https://web.archive.org/web/20180911075211/https://www.stallman.org/archives/2018-jul-oct.html#17_July_2018_(The_bullshitter's_flirting)">about</a> <a href="https://web.archive.org/web/20180911075211/https://www.stallman.org/archives/2018-jul-oct.html#21_August_2018_(Age_and_attraction)">sexual</a> <a href="https://web.archive.org/web/20180924231708/https://stallman.org/archives/2018-jul-oct.html#23_September_2018_(Cody_Wilson)">morality</a> <a href="https://web.archive.org/web/20180919100154/https://stallman.org/antiglossary.html#assult">escalated</a> <a href="https://web.archive.org/web/20181113161736/https://www.stallman.org/archives/2018-sep-dec.html#6_November_2018_(Sex_according_to_porn)">for</a> <a href="https://web.archive.org/web/20190325024048/https://stallman.org/archives/2019-jan-apr.html#14_February_2019_(Respecting_peoples_right_to_say_no)">the</a> <a href="https://www.stallman.org/archives/2019-may-aug.html#11_June_2019_(Stretching_meaning_of_terms)">worse</a> <a href="https://web.archive.org/web/20190801201704/https://stallman.org/archives/2019-may-aug.html#12_June_2019_(Declining_sex_rates)">over</a> <a href="https://web.archive.org/web/20190801201704/https://stallman.org/archives/2019-may-aug.html#30_July_2019_(Al_Franken)">the</a> <a href="https://web.archive.org/web/20190903050208/https://stallman.org/archives/2019-jul-oct.html#27_August_2019_(Me-too_frenzy)">last</a> <a href="https://web.archive.org/web/20191011023557/https://stallman.org/archives/2019-jul-oct.html#21_September_2019_(Sex_workers)">few</a> <a href="https://web.archive.org/web/20180924231708/https://stallman.org/archives/2018-jul-oct.html#23_September_2018_(Cody_Wilson)">years</a>. When the escalation started, I still considered RMS both a friend and colleague, and I attempted to argue with him at length to convince him that some of his positions were harmful to sexual assault survivors and those who are sex trafficked, and to the people who devote their lives in service to such individuals. More importantly to the FSF, I attempted to persuade RMS that launching a controversial campaign on sexual behavior and morality was counter to his and FSF's mission to advance software freedom, and told RMS that my duty as an FSF Director was to assure the best outcome for the FSF, which <acronym title="in my opinion">IMO</acronym> didn't include having a leader who made such statements. Not only is human sexual behavior not a topic on which RMS has adequate academic expertise, but also his positions appear to ignore significant research and widely available information on the subject. Many of his comments, while occasionally politically intriguing, lack empathy for people who experienced trauma.
</p>
<p>
IMO, this is not and has never been a Free Speech issue. I do believe freedom of speech links directly to software freedom: indeed, I see the freedom to publish software under Free licenses as almost a corollary to the freedom of speech. However, we do not need to follow leadership from those whose views we fundamentally disagree. Moreover, organizations need not and should not elevate spokespeople and leaders who speak regularly on unrelated issues that organizations find do not advance their mission, and/or that alienate important constituents. I, like many other software freedom leaders, curtail my public comments on issues not related to <acronym title="Free and Open Source Software">FOSS</acronym>. (Indeed, I would not even be commenting on <em>this issue</em> if it had not become a central issue of concern to the software freedom community.) Leaders have power, and they must exercise the power of their words with <a href="https://lwn.net/Articles/770966/">restraint, not with impunity</a>.
</p>
<p>
RMS has consistently argued that there was a campaign of “prudish intimidation” — seeking to keep him quiet about his views on sexuality. After years of conversing with RMS about how his non-software-freedom views were a distraction, an indulgence, and downright problematic, his general response was to make even more public comments of this nature. The issue is not about RMS' right to say what he believes, nor is it even about whether or not you agree or disagree with RMS' statements. The question is whether an organization should have a designated leader who is on a sustained, public campaign advocating about an unrelated issue that many consider controversial. It really doesn't matter what your view about the controversial issue is; a leader who refuses to stop talking loudly about unrelated issues eventually creates an untenable distraction from the radical activism you're actively trying to advance. The message of universal software freedom is a radical cause; it's basically impossible for one individual to effectively push forward two unrelated controversial agendas at once. In short, the radical message of software freedom became overshadowed by RMS' radical views about sexual morality.
</p>
<p>
And here is where I say the thing that may infuriate many but it's what I believe: I think RMS took a useful step by resigning some of his leadership roles at the FSF. I thank RMS for taking that step, and I wish the FSF Directors well in their efforts to assure that the FSF becomes a welcoming organization to all who care about universal software freedom. The <a href="https://www.fsf.org/about/">FSF's mission</a> is essential to our technological future, and we should all support that mission. I care deeply about that mission myself and have worked and will continue to work in our community in the best interest of the mission.
</p>
<p>
I'm admittedly struggling to find a way to work again with RMS, given his views on sexual morality and his behaviors stemming from those views. I explicitly do not agree with <a href="https://web.archive.org/web/20180919100154/https://stallman.org/antiglossary.html#assult">this “(re-)definition” of sexual assault</a>. Furthermore, I believe uninformed statements about sexual assault are irresponsible and cause harm to victims. #MeToo is <strong><a href="https://web.archive.org/web/20190903050208/https://stallman.org/archives/2019-jul-oct.html#27_August_2019_(Me-too_frenzy)">not a “frenzy”</a></strong>; it is a global movement by individuals who have been harmed seeking to hold both bad actors <em>and</em> society-at-large accountable for ignoring systemic wrongs. Nevertheless, I still am proud of the <a href="https://www.gnu.org/philosophy/freedom-or-power.en.html">essay that I co-wrote with RMS</a> and still find <a href="https://www.gnu.org/gnu/manifesto.en.html">many</a> <a href="https://www.gnu.org/philosophy/free-sw.html">of</a> <a href="https://www.gnu.org/philosophy/why-free.html">RMS'</a> <a href="https://www.gnu.org/philosophy/pragmatic.html">other</a> <a href="https://www.gnu.org/philosophy/microsoft-old.html">essays</a> <a href="https://www.gnu.org/philosophy/gpl-american-way.html">compelling</a>, <a href="https://www.gnu.org/licenses/why-not-lgpl.html">important</a>, <a href="https://www.gnu.org/philosophy/stallman-kth.en.html">and</a> <a href="https://www.gnu.org/philosophy/who-does-that-server-really-serve.en.html">relevant</a>.
</p>
<p>
I want the FSF to succeed in its mission and enter a new era of accomplishments. I've spent the last 22 years, without a break, dedicating substantial time, effort, care and loyalty to the various FSF roles that I've had: including employee, volunteer, at-large Director, and Voting Member. Even though my duties to the FSF are done, and my relationship with the FSF is no longer formal, I still think the FSF is a valuable institution worth helping and saving, specifically because the FSF was founded for a mission that I deeply support. And we should also realize that RMS — a human being (who is flawed like the rest of us) — invented that mission.
</p>
<p>
As culture change becomes more rapid, I hope we can find reasonable nuance and moderation on our complex analysis about people and their disparate views, while we also hold individuals fully accountable for their actions. That's the difficulty we face in the post-post-modern culture of the early twenty-first century. Most importantly, I believe we must find a way to stand firm for software freedom while also making a safe environment for victims of sexual assault, sexual abuse, gaslighting, and other deplorable actions.
</p>
<p class="bottomAttributionWithDate">
Posted on Tuesday 15 October 2019 at 09:11 by Bradley M. Kuhn.
</p>
<p class="comments">
Submit comments on this post to <a href="mailto:bkuhn@ebb.org">&lt;bkuhn@ebb.org&gt;</a>.
</p>
<div id="prevlink">
<a class="previous" href="/bkuhn/blog/2019/05/23/github-sponsors.html"><strong>Previous</strong>: Chasing Quick Fixes To Sustainability</a>
</div>
</div>
</div>
<p id="footer"></p>
<hr />
<br />
<p>
<a href="http://creativecommons.org/licenses/by-sa/3.0/us/" rel="license"><img alt="Creative Commons License" src="http://i.creativecommons.org/l/by-sa/3.0/us/88x31.png" style="border-width:10" /></a> This website and all documents on it are licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0/us/" rel="license">Creative Commons Attribution-Share Alike 3.0 United States License</a> .
</p>
<hr />
<p>
<code>#include &lt;std/disclaimer.h&gt;</code><br />
<code>use Standard::Disclaimer;</code><br />
<code>from standard import disclaimer</code><br />
<code>SELECT full_text FROM standard WHERE type = 'disclaimer';</code>
</p>
<p>
Both previously and presently, I have been employed by and/or done work for various organizations that also have views on Free, Libre, and Open Source Software. As should be blatantly obvious, this is my website, not theirs, so please do not assume views and opinions here belong to any such organization. Since I do co-own ebb.org with my wife, it may not be so obvious that these aren't her views and opinions, either.
</p>
<p align="right">
— bkuhn
</p>
<hr />
<p>
ebb <sup></sup> is a service mark of Bradley M. Kuhn.
</p>
<address>
<a href="http://ebb.org/bkuhn/">Bradley M. Kuhn</a> <a href="mailto:bkuhn@ebb.org">&lt;bkuhn@ebb.org&gt;</a>
</address>
</div>
</body>
</html>

@ -1,8 +0,0 @@
{
"title": "How to Build a Terrarium | eHow",
"byline": "Lucy Akins",
"dir": null,
"excerpt": "Glass cloche terrariums are not only appealing to the eye, but they also preserve a bit of nature in your home and serve as a simple, yet beautiful, piece of art. Closed terrariums are easy to care for, as they retain much of their own moisture and provide a warm environment with a consistent level of humidity. You wont have to water the...",
"siteName": "eHow",
"readerable": true
}

@ -1,109 +0,0 @@
<div id="readability-page-1" class="page">
<div>
<header>
</header>
<div>
<p>Glass cloche terrariums are not only appealing to the eye, but they also preserve a bit of nature in your home and serve as a simple, yet beautiful, piece of art. Closed terrariums are easy to care for, as they retain much of their own moisture and provide a warm environment with a consistent level of humidity. You wont have to water the terrariums unless you see that the walls are not misting up. Small growing plants that dont require a lot of light work best such as succulents, ferns, moss, even orchids.</p>
<figure> <img src="http://img-aws.ehowcdn.com/640/cme/photography.prod.demandstudios.com/16149374-814f-40bc-baf3-ca20f149f0ba.jpg" alt="Glass cloche terrariums" title="Glass cloche terrariums" data-credit="Lucy Akins " longdesc="http://s3.amazonaws.com/photography.prod.demandstudios.com/16149374-814f-40bc-baf3-ca20f149f0ba.jpg" /> </figure>
<figcaption class="caption"> Glass cloche terrariums (Lucy Akins) </figcaption>
</div>
<div>
<p><span>What You'll Need:</span></p>
<ul>
<li>Cloche</li>
<li>Planter saucer, small shallow dish or desired platform</li>
<li>Floral foam oasis</li>
<li>Ruler </li>
<li>Spoon</li>
<li>Floral wire pins or paper clips</li>
<li>Small plants (from a florist or nursery)</li>
<li>Moss</li>
<li>Tweezers</li>
<li>Other small decorative items (optional)</li>
</ul>
</div>
<div>
<div>
<p><span>Step 1</span></p>
<p>Measure the circumference of your cloche and cut the foam oasis about 3/4 inch (2 cm) smaller. Place the foam oasis into a container full of water and allow to soak until it sinks to the bottom. Dig out a hole on the oasis large enough to fit your plant, being careful not to pierce all the way through to the bottom.</p>
</div>
<figure> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/fc249ef6-4d27-41b4-8c21-15f7a8512b50.jpg" alt="Dig a hole in the oasis." data-credit="Lucy Akins" /> </figure>
<figcaption class="caption"> Dig a hole in the oasis. (Lucy Akins) </figcaption>
</div>
<div>
<div>
<p><span>Step 2</span></p>
<p>Insert your plant into the hole.</p>
</div>
<figure> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/aae11d4d-a4aa-4251-a4d9-41023ebf6d84.jpg" alt="Orchid in foam oasis" data-credit="Lucy Akins" /> </figure>
<figcaption class="caption"> Orchid in foam oasis (Lucy Akins) </figcaption>
</div>
<div>
<div>
<p><span>Step 3</span></p>
<p>You can add various plants if you wish.</p>
</div>
<figure> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/7afdfa1e-da74-44b5-b89c-ca8123516272.jpg" alt="Various foliage" data-credit="Lucy Akins" /> </figure>
<figcaption class="caption"> Various foliage (Lucy Akins) </figcaption>
</div>
<div>
<div>
<p><span>Step 4</span></p>
<p>Using floral pins, attach enough moss around the oasis to cover it.</p>
</div>
<figure> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/4f6612c0-316a-4c74-bb03-cb4e778f6d72.jpg" alt="Attach moss." data-credit="Lucy Akins" /> </figure>
<figcaption class="caption"> Attach moss. (Lucy Akins) </figcaption>
</div>
<div>
<div>
<p><span>Step 5</span></p>
<p>Gently place the cloche over the oasis. The glass may push some of the moss upward, exposing some of the foam.</p>
</div>
<figure> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/eeb1e0b4-e573-40a3-8db1-2c76f0b13b84.jpg" alt="Place cloche over oasis." data-credit="Lucy Akins" /> </figure>
<figcaption class="caption"> Place cloche over oasis. (Lucy Akins) </figcaption>
</div>
<div>
<div>
<p><span>Step 6</span></p>
<p>Simply pull down the moss with tweezers or insert more moss to fill in the empty spaces.</p>
</div>
<figure> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/812d4649-4152-4363-97c0-f181d02e709a.jpg" alt="Rearrange moss." data-credit="Lucy Akins" /> </figure>
<figcaption class="caption"> Rearrange moss. (Lucy Akins) </figcaption>
</div>
<div>
<div>
<p><span>Step 7</span></p>
<p>You can use any platform you wish. In this case, a small saucer was used.</p>
</div>
<figure> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/0cb3988c-9318-47d6-bc9c-c798da1ede72.jpg" alt="Place cloche on a platform to sit on." data-credit="Lucy Akins" /> </figure>
<figcaption class="caption"> Place cloche on a platform to sit on. (Lucy Akins) </figcaption>
</div>
<div>
<div>
<p><span>Step 8</span></p>
<p>This particular terrarium rests on a planter saucer and features a small white pumpkin.</p>
</div>
<figure> <img src="http://img-aws.ehowcdn.com/640/cme/photography.prod.demandstudios.com/e3e18f0b-ab2c-4ffb-9988-a1ea63faef8b.jpg" alt="Cloche placed on a terracotta saucer" data-credit="Lucy Akins" /> </figure>
<figcaption class="caption"> Cloche placed on a terracotta saucer (Lucy Akins) </figcaption>
</div>
<div>
<div>
<p><span>Step 9</span></p>
<p>This particular terrarium was placed on a wood slice and a little toy squirrel was placed inside to add a little whimsy.</p>
</div>
<figure> <img src="http://img-aws.ehowcdn.com/640/cme/photography.prod.demandstudios.com/2cd79f8d-0d16-4573-8861-e47fb74b0638.jpg" alt="Placed on a wooden slice" data-credit="Lucy Akins" /> </figure>
<figcaption class="caption"> Placed on a wooden slice (Lucy Akins) </figcaption>
</div>
<div>
<div>
<p><span>Finished Terrarium</span></p>
<p>Displayed alone or in a group, these pretty arrangements allow you to add a little nature to your decor or tablescape.</p>
</div>
<figure> <img src="http://img-aws.ehowcdn.com/640/cme/photography.prod.demandstudios.com/78670312-8636-4c42-a75c-3029f7aa6c73.jpg" alt="Cloche terrarium" data-credit="Lucy Akins" /> </figure>
<figcaption class="caption"> Cloche terrarium (Lucy Akins) </figcaption>
</div>
<section id="FeaturedTombstone" data-module="rcp_tombstone">
<h2>Featured</h2>
</section>
</div>
</div>

@ -1,934 +0,0 @@
<!DOCTYPE html>
<!--[if IE]><![endif]-->
<html class="Crafts en-US" lang="en-US" xmlns:fb="http://www.facebook.com/2008/fbml" xmlns:og="http://ogp.me/ns#" data-channel="crafts" itemscope="" itemtype="http://schema.org/Article">
<head>
<meta charset="utf-8"/>
<meta name="description" content="How to Build a Terrarium. Glass cloche terrariums are not only appealing to the eye, but they also preserve a bit of nature in your home and serve as a simple, yet beautiful, piece of art. Closed terrariums are easy to care for, as they retain much of their own moisture and provide a warm environment with a consistent level of humidity. You..."/>
<meta name="format-detection" content="telephone=no"/>
<meta name="y_key" content="4d4cb4ef1ad334cb"/>
<meta name="alexaVerifyID" content="xlJch1oUHrXNtOQBpiuUCV0-rlc"/>
<meta name="cachetime" content="201504281431"/>
<meta name="p:domain_verify" content="4d1387210be25228f0c4692cc47db05b"/>
<meta name="cme_last_updated" content="2015-04-28T11:54:11.401000"/>
<meta name="publishdate" content="05/29/2007 09:49:00"/>
<meta name="last_rcp_import" content="2015-04-27T19:34:40.179876"/>
<meta name="articleimage" content="http://s3.amazonaws.com/photography.prod.demandstudios.com/16149374-814f-40bc-baf3-ca20f149f0ba.jpg"/>
<link href="http://www.ehow.com/how_2042752_build-terrarium.html" rel="canonical"/>
<link type="application/rss+xml" href="http://www.ehow.com/feed/ehow-crafts.rss" rel="alternate" title="Subscribe to our RSS Feed"/>
<meta name="studioid" scheme="DMINSTR2" content="f941e4cd-a49e-4948-b244-677c15ff8b7f"/>
<meta name="contentid" scheme="DMINSTR2" content="2042752"/>
<meta name="category" scheme="DMINSTR2" content="Crafts"/>
<meta name="subcategory" scheme="DMINSTR2" content="Other DIY Crafts"/>
<meta name="subsubcat" scheme="DMINSTR2" content="Other DIY Projects"/>
<meta name="subpagetype" scheme="DMINSTR2" content="topic_view"/>
<meta name="exp_page" scheme="DMINSTR2" content=""/>
<meta name="wa_pchn" scheme="DMINSTR2" content="Crafts"/>
<meta name="pagetype" scheme="DMINSTR2" content="inline"/>
<meta name="wa_lr" scheme="DMINSTR2" content="en-US"/>
<meta name="siteid" scheme="DMINSTR2" content="EHWC"/>
<meta name="exp_name" scheme="DMINSTR2" content="desktop:default"/>
<meta name="wa_un" scheme="DMINSTR2" content=""/>
<meta name="wa_isreg" scheme="DMINSTR2" content=""/>
<meta name="wa_lgdin" scheme="DMINSTR2" content=""/>
<meta name="wa_clvl" scheme="DMINSTR2" content="1"/>
<meta name="wa_lgsrc" scheme="DMINSTR2" content="1"/>
<meta name="author" scheme="DMINSTR2" content="Lucy Akins"/>
<meta name="author_image" content=""/>
<meta name="twitter:card" content="summary"/>
<meta name="twitter:site" content="@ehow"/>
<meta property="og:image" content="http://img-aws.ehowcdn.com/200x200/cme/photography.prod.demandstudios.com/16149374-814f-40bc-baf3-ca20f149f0ba.jpg"/>
<meta property="og:url" content="http://www.ehow.com/how_2042752_build-terrarium.html"/>
<meta property="og:type" content="article"/>
<meta property="og:title" content="How to Build a Terrarium | eHow"/>
<meta property="og:description" content="Glass cloche terrariums are not only appealing to the eye, but they also preserve a bit of nature in your home and serve as a simple, yet beautiful, piece of art. Closed terrariums are easy to care for, as they retain much of their own moisture and provide a warm environment with a consistent level of humidity. You wont have to water the..."/>
<meta property="fb:app_id" content="63203377906"/>
<meta property="fb:ttl" content="604800"/>
<meta property="og:site_name" content="eHow"/>
<title>How to Build a Terrarium (with Pictures) | eHow</title>
<link type="image/x-icon" href="http://v5-static.ehowcdn.com/favicon.ico" rel="shortcut icon apple-touch-icon"/>
<link rel="stylesheet" type="text/css" href="http://dynamic02.ehowcdn.com/services/modules/css/common_header,corporate-skin,inline/fa923205/"/>
<!--[if lt IE 9]><link href="http://v5-static.ehowcdn.com/ui/stylesheets/layout/ie.css" rel="stylesheet" /><script src="http://v5-static.ehowcdn.com/ui/scripts/ie_hacks/header.js"></script><![endif]-->
<!--[if IE 9]><link href="http://v5-static.ehowcdn.com/ui/stylesheets/layout/ie9.css" rel="stylesheet" /><![endif]-->
<script>
window._omnitureLinkSetDomain = 'extended.dmtracker.com';
</script>
<script>
var dmjs = dmjs || {};
dmjs.global = dmjs.global || {};
dmjs.global.glbBaseUrl = "http:\/\/www.ehow.com";
dmjs.global.facebook = {
"app_id": "63203377906",
"api_version": "v2.0",
"login_service": "http:\/\/www.ehow.com\/services\/fb\/login\/",
"comment_service": "http:\/\/www.ehow.com\/services\/comment\/refresh\/"
};
dmjs.urls = {
"cdn": "http:\/\/v5-static.ehowcdn.com",
"sitelife": "http:\/\/sitelife.ehow.com\/ver1.0\/",
"dmtracker": "vs.dmtracker.com"
};
dmjs.global.IN_PRODUCTION = true;
dmjs.global.darkhorse = true;
dmjs.global.pool = "default";
dmjs.global.channel = "crafts";
dmjs.global.skin = "crafts";
dmjs.global.facebook.xid = "aG93XzIwNDI3NTJfYnVpbGQtdGVycmFyaXVtLmh0bWw";
dmjs.global.facebook.associate_on_comment = false;
dmjs.global._adChannel = "craft_other_diy,text,suggested7";
dmjs.global.IsSlideStep = false;
dmjs.global.fixed_category = [{
"id": 5352,
"title": "Crafts",
"url": "\/crafts\/"
}, {
"id": 5426,
"title": "Other DIY Crafts",
"url": "\/diy-crafts\/"
}, {
"id": 5429,
"title": "Other DIY Projects",
"url": "\/diy-projects\/"
}];
dmjs.global.SlideShowTest = {
"_id": "www.ehow.com\/how_16357_make-paper-sunflowers.html",
"dimensions": "none",
"image": {
"url": "http:\/\/s3.amazonaws.com\/uploadedimages.demandmedia\/sunflowers-1.jpg"
},
"subtitle": "",
"title": "How to Make Paper Sunflowers",
"type": "Article",
"url": "\/how_16357_make-paper-sunflowers.html"
};
dmjs.global.contributor = "www.demandstudios.com\/contributor\/3959b24c-24bb-447f-a676-55ce0b6e5519";
dmjs.global.articleType = "Article";
dmjs.global.articleID = 2042752;
/* Add xdomain first event helper to be added before ad event listeners are added */
dmjs.xdomainFirstEventListener = true;
if (window.addEventListener) {
window.addEventListener("message", function(event) {
if (typeof xdomain != 'undefined' && typeof xdomain.dmjsFirstEventListener != 'undefined') {
xdomain.dmjsFirstEventListener(event);
}
}, false);
} else {
window.attachEvent("message", function(event) {
if (typeof xdomain != 'undefined' && typeof xdomain.dmjsFirstEventListener != 'undefined') {
xdomain.dmjsFirstEventListener(event);
}
});
}
</script>
<!--[if lt IE 9]>
<script src="http://v5-static.ehowcdn.com/content/compressed/common_header-646656f9.js" ></script>
<![endif]-->
<script src="http://dynamic01.ehowcdn.com/services/modules/js/gpt,common_header_slim/4eb05a96/"></script>
<script class="kxct" data-id="JWC_M4Hq" data-timing="async" data-version="1.9" type="text/javascript">
window.Krux || ((Krux = function() {
Krux.q.push(arguments)
}).q = []);
(function() {
var k = document.createElement('script');
k.type = 'text/javascript';
k.async = true;
var m, src = (m = location.href.match(/\bkxsrc=([^&]+)/)) && decodeURIComponent(m[1]);
k.src = /^https?:\/\/([a-z0-9_\-\.]+\.)?krxd\.net(:\d{1,5})?\//i.test(src) ? src : src === "disable" ? "" : (location.protocol === "https:" ? "https:" : "http:") + "//cdn.krxd.net/controltag?confid=JWC_M4Hq";
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(k, s);
}());
</script>
<script>
window.Krux || ((Krux = function() {
Krux.q.push(arguments);
}).q = []);
(function() {
function retrieve(n) {
var m, k = 'kx' + n;
if (window.localStorage) {
return window.localStorage[k] || "";
} else if (navigator.cookieEnabled) {
m = document.cookie.match(k + '=([^;]*)');
return (m && unescape(m[1])) || "";
} else {
return '';
}
}
Krux.user = retrieve('user');
Krux.segments = retrieve('segs') && retrieve('segs').split(',') || [];
})();
</script>
<script type="text/javascript">
var GPT = [];
googletag.cmd.push(function() {
window.slot_define_string = "6117/dmd.ehow/crafts/otherdiycrafts/otherdiyprojects";
GPT['300x250_300x600'] = googletag.defineSlot('/6117/dmd.ehow/crafts/otherdiycrafts/otherdiyprojects', [
[300, 250],
[300, 600]
], '300x250_300x600').addService(googletag.pubads());
GPT['728x90_990x90_970x250'] = googletag.defineSlot('/6117/dmd.ehow/crafts/otherdiycrafts/otherdiyprojects', [
[728, 90],
[990, 90],
[970, 250]
], '728x90_990x90_970x250').addService(googletag.pubads());
GPT['outofpage'] = googletag.defineOutOfPageSlot('/6117/dmd.ehow/crafts/otherdiycrafts/otherdiyprojects', 'outofpage').addService(googletag.pubads()).setCollapseEmptyDiv(true, true);
googletag.pubads().setTargeting('cat', 'crafts');
googletag.pubads().setTargeting('scat', 'otherdiycrafts');
googletag.pubads().setTargeting('sscat', 'otherdiyprojects');
googletag.pubads().setTargeting('x', '7');
googletag.pubads().setTargeting('art', '2042752');
googletag.pubads().setTargeting('ctype', 'step_articles');
googletag.pubads().setTargeting('vid', '0');
googletag.pubads().setTargeting('blog', '0');
googletag.pubads().setTargeting('ad_chan', 'craft_other_diy');
window.gpt_targeting_vars = {
"cat": "crafts",
"scat": "otherdiycrafts",
"sscat": "otherdiyprojects",
"x": "7",
"art": 2042752,
"ctype": "step_articles",
"vid": 0,
"blog": 0,
"ad_chan": "craft_other_diy"
};
googletag.pubads().setTargeting('dc_ref', top.window.location.href.replace('#', '-'));
googletag.pubads().setTargeting("envr", ""); //for production. set envr to empty string
googletag.pubads().setTargeting("ksg", Krux.segments);
googletag.pubads().setTargeting("kuid", Krux.user);
googletag.pubads().collapseEmptyDivs();
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});
</script>
<script>
dataLayer = [{
'content_category': 'Crafts',
'content_subCategory': 'Other DIY Crafts',
'content_subSubCategory': 'Other DIY Projects',
'content_pageType': 'inline',
'content_subPageType': 'topic_view',
'content_channel': 'crafts',
'content_author': 'Lucy Akins',
'content_publishDate': '05/29/2007 09:49:00',
'content_name': 'How to Build a Terrarium (with Pictures) | eHow',
'content_hasImages': 'True',
'content_experience': 'desktop:default',
'httpStatusCode': '200'
}];
</script>
<!--
<PageMap>
<DataObject type="meta">
<Attribute name="articlethumbnail"><![CDATA[]]></Attribute>
<Attribute name="articletype"><![CDATA[professional]]></Attribute>
</DataObject>
</PageMap>
-->
</head>
<body class="Crafts TopicView " data-skin="crafts">
<!--[if IE 7]>
<script src="http://v5-static.ehowcdn.com/ui/scripts/ie7_notification.js" ></script> <![endif]-->
<noscript>
<iframe src="//www.googletagmanager.com/ns.html?id=GTM-K7RB82" height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>
<script>
(function(w, d, s, l, i) {
w[l] = w[l] || [];
w[l].push({
'gtm.start': new Date().getTime(),
event: 'gtm.js'
});
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s),
dl = l != 'dataLayer' ? '&l=' + l : '';
j.async = true;
j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl;
f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'dataLayer', 'GTM-K7RB82');
</script>
<header id="Header" data-section="header">
<section class="topContainer inner-container page-header clearfix">
<a href="http://www.ehow.com/" class="image corp-logo fl"><img src="http://v5-static.ehowcdn.com/media/images/logos/logo-stamp.png" width="134" height="38" alt="eHow Logo"/></a>
<form method="get" action="http://www.ehow.com/search.html" id="searchHeader" class="Search ">
<input type="text" name="s" data-type="searchinput" class="input fl headline4"/>
<input name="skin" type="hidden" value="corporate"/>
<input name="t" type="hidden" value="all"/>
<input type="submit" value="Search" class="bttn submit headline5 head"/> </form>
</section>
<nav id="primaryNav" role="navigation">
<ul class="inner-container channelListing clearfix skin-crafts">
<li class="list js-list"><a href="http://www.ehow.com/ehow-mom/" class="mom head title channel gtm_headerNavmom" data-channel="mom">mom</a></li>
<li class="list js-list"><a href="http://www.ehow.com/ehow-style/" class="style head title channel gtm_headerNavstyle" data-channel="style">style</a></li>
<li class="list js-list"><a href="http://www.ehow.com/ehow-food/" class="food head title channel gtm_headerNavfood" data-channel="food">food</a></li>
<li class="list js-list"><a href="http://www.ehow.com/ehow-tech/" class="tech head title channel gtm_headerNavtech" data-channel="tech">tech</a></li>
<li class="list js-list"><a href="http://www.ehow.com/ehow-home/" class="home head title channel gtm_headerNavhome" data-channel="home">home</a></li>
<li class="list js-list"><a href="http://www.ehow.com/ehow-money/" class="money head title channel gtm_headerNavmoney" data-channel="money">money</a></li>
<li class="list js-list"><a href="http://www.ehow.com/ehow-crafts/" class="crafts head title channel gtm_headerNavcrafts" data-channel="crafts">crafts</a></li>
<li class="list js-list more"><a class="head title more js-more" data-channel="more" href="#">More<span class="js-icon more-icon elegant-icons">3</span></a>
<div class="more_dropdown">
<div class="moreLinks">
<div class="headline2 head mg-4">Browse Articles &amp; Videos By Category</div>
<ul class="list list-0">
<li class="item"><a href="http://www.ehow.com/arts/" class="headline5 title target">Arts &amp; Entertainment</a></li>
<li class="item"><a href="http://www.ehow.com/business/" class="headline5 title target">Business</a></li>
<li class="item"><a href="http://www.ehow.com/careers/" class="headline5 title target">Careers &amp; Work</a></li>
<li class="item"><a href="http://www.ehow.com/cars/" class="headline5 title target">Cars</a></li>
<li class="item"><a href="http://www.ehow.com/computers/" class="headline5 title target">Computers</a></li>
<li class="item"><a href="http://www.ehow.com/crafts/" class="headline5 title target">Crafts</a></li>
<li class="item"><a href="http://www.ehow.com/cultures/" class="headline5 title target">Culture &amp; Society</a></li>
</ul>
<ul class="list list-1">
<li class="item"><a href="http://www.ehow.com/education/" class="headline5 title target">Education</a></li>
<li class="item"><a href="http://www.ehow.com/electronics/" class="headline5 title target">Electronics</a></li>
<li class="item"><a href="http://www.ehow.com/fashion/" class="headline5 title target">Fashion, Style &amp; Personal Care</a></li>
<li class="item"><a href="http://www.ehow.com/food/" class="headline5 title target">Food &amp; Drink</a></li>
<li class="item"><a href="http://www.ehow.com/health/" class="headline5 title target">Health</a></li>
<li class="item"><a href="http://www.ehow.com/hobbies-games/" class="headline5 title target">Hobbies, Games &amp; Toys</a></li>
<li class="item"><a href="http://www.ehow.com/holidays/" class="headline5 title target">Holidays &amp; Celebrations</a></li>
</ul>
<ul class="list list-2">
<li class="item"><a href="http://www.ehow.com/home/" class="headline5 title target">Home &amp; Garden</a></li>
<li class="item"><a href="http://www.ehow.com/internet/" class="headline5 title target">Internet</a></li>
<li class="item"><a href="http://www.ehow.com/legal/" class="headline5 title target">Legal</a></li>
<li class="item"><a href="http://www.ehow.com/parenting/" class="headline5 title target">Parenting</a></li>
<li class="item"><a href="http://www.ehow.com/parties/" class="headline5 title target">Parties &amp; Entertaining</a></li>
<li class="item"><a href="http://www.ehow.com/personal-finance/" class="headline5 title target">Personal Finance</a></li>
<li class="item"><a href="http://www.ehow.com/pets/" class="headline5 title target">Pets</a></li>
</ul>
<ul class="list list-3">
<li class="item"><a href="http://www.ehow.com/relationships/" class="headline5 title target">Relationships &amp; Family</a></li>
<li class="item"><a href="http://www.ehow.com/sports/" class="headline5 title target">Sports &amp; Fitness</a></li>
<li class="item"><a href="http://www.ehow.com/travel/" class="headline5 title target">Travel</a></li>
<li class="item"><a href="http://www.ehow.com/weddings/" class="headline5 title target">Weddings</a></li>
</ul>
</div>
<div class="sponsored_programs">
<ul class="unstyled">
<li class="item">
<a href="http://www.ehow.com/ehow-indoor/" class="diyhome"></a>
</li>
<li class="item">
<a href="http://www.ehow.com/ehow-outdoor/" class="diyoutdoor"></a>
</li>
</ul>
</div>
</div>
</li>
</ul>
</nav>
</header>
<div class="inner-container">
<!-- gpt slot 728x90_990x90_970x250 -->
<div class="bannerAd mod" data-module="gpt-ad-728x90_990x90_970x250" id="728x90_990x90_970x250">
<script type="text/javascript">
googletag.cmd.push(function() {
googletag.display('728x90_990x90_970x250');
});
</script>
</div>
<!-- gpt slot outofpage -->
<div class="AdUnitOutOfPage mod" data-module="gpt-ad-outofpage" id="outofpage">
<script type="text/javascript">
googletag.cmd.push(function() {
googletag.display('outofpage');
});
</script>
</div>
</div>
<section id="Body" class="InlineTemplate FLC" data-page-id="inlinetemplate" data-section="body">
<script id="_ehow-com-hnh"></script>
<ol class="breadcrumbs unstyled inline" itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb" data-module="breadcrumb">
<li class="list">
<a href="http://www.ehow.com/" rel="home" itemprop="url" class="title"> <span itemprop="title">eHow</span> </a>
</li>
<li class="list" itemprop="child" itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb">
<a href="http://www.ehow.com/crafts/" rel="directory" itemprop="url" class="title"> <span itemprop="title">Crafts</span> </a>
</li>
<li class="list" itemprop="child" itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb">
<a href="http://www.ehow.com/crafts/diy-crafts/" rel="directory" itemprop="url" class="title"> <span itemprop="title">Other DIY Crafts</span> </a>
</li>
<li class="list" itemprop="child" itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb">
<a href="http://www.ehow.com/crafts/diy-crafts/diy-projects/" rel="directory" itemprop="url" class="title"> <span itemprop="title">Other DIY Projects</span> </a>
</li>
<li class="list" itemprop="child" itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb">
<a href="http://www.ehow.com/how_2042752_build-terrarium.html" rel="directory" itemprop="url" class="title"> <span itemprop="title">How to Build a Terrarium</span> </a>
</li>
</ol>
<!-- gpt slot 990x50 -->
<div class="col-main">
<header class="page-head bordered">
<h1 class="headline1 head mg-2" itemprop="headline">
How to Build a Terrarium </h1>
<div data-type="AuthorProfile" itemprop="author" itemscope="" itemtype="http://www.schema.org/Person">
<script type="text/html" data-element="template">
<footer class="AuthorProfileWrap footer">
<div class="AuthorProfileModal" data-element="modal">
<div class="wrapper">
<div class="ModalClose close" data-action="hideModal" data-element="closeButton">X</div> <img src="http://img-aws.ehowcdn.com/100x100/studio-image/ver1.0/Content/images/store/3/11/a3407c7f-fdf9-4ae6-8bac-48007b0109ea.Medium.jpg" onerror="this.className = &#039;hide&#039;" />
<div class="headline3 head mg-2" itemprop="name"> Lucy Akins </div>
<p class="bio"> Lucy Akins is an artist/photographer/blogger living in Toronto, Ontario. Her blog, Craftberry Bush, has blossomed into a business, having gained domestic and international recognition. Her work has been published in several magazines, including: Cottages and Bungalows, American Farmlife Style, National Geographic Kids, Artful Blogger, Somerset Life, and most recently graced the cover of Somerset Home. </p>
<p class="portfolio"> <a class="profile" itemprop="url" rel="author" href="http://www.demandstudios.com/profile-3959b24c-Lucy-Akins/" target="_blank">View my portfolio</a> </p>
</div>
</div>
</footer>
</script>
<div class="post-meta clearfix headline6 mg-2 ">
<div class="meta">
<a class="byline trigger gtm_contributorByline head-alt nb-name" rel="author" itemprop="author" href="/contributor/lucy_akins/" target="_top"> <span itemprop="name">By Lucy Akins</span> </a>
<div class="nb-byline">eHow Contributor</div>
</div>
</div>
<div class="article-meta"> </div>
<div data-element="container"></div>
</div>
<div class="clearfix">
<ul class="social-icons js-social-icons inline flcb" data-url="http://www.ehow.com/how_2042752_build-terrarium.html">
<li class="item btnPinterest">
<a class="js-btn btn gtm_socialShare" data-social="pinterest" data-url="http://www.ehow.com/how_2042752_build-terrarium.html?p=d" data-title="How to Build a Terrarium" data-media="http://s3.amazonaws.com/photography.prod.demandstudios.com/16149374-814f-40bc-baf3-ca20f149f0ba.jpg"> <span class="social-icon elegant-icons"></span> <span class="action js-pins">Pin</span> </a>
</li>
<li class="item btnFacebook">
<a class="js-btn btn gtm_socialShare" data-social="facebook" data-url="http://www.ehow.com/how_2042752_build-terrarium.html" data-title="How to Build a Terrarium" data-media="http://s3.amazonaws.com/photography.prod.demandstudios.com/16149374-814f-40bc-baf3-ca20f149f0ba.jpg" data-description="Glass cloche terrariums are not only appealing to the eye, but they also preserve a bit of nature in your home and serve as a simple, yet beautiful, piece of art. Closed terrariums are easy to care for, as they retain much of their own moisture and provide a warm environment with a consistent level of humidity. You wont have to water the..."> <span class="social-icon elegant-icons"></span> <span class="action js-facebook_shares">Share</span> </a>
</li>
<li class="item btnTwitter">
<a class="js-btn btn gtm_socialShare" data-social="twitter" data-url="http://www.ehow.com/how_2042752_build-terrarium.html" data-title="How to Build a Terrarium" data-media="http://s3.amazonaws.com/photography.prod.demandstudios.com/16149374-814f-40bc-baf3-ca20f149f0ba.jpg"> <span class="social-icon elegant-icons"></span> <span class="action js-tweets">Tweet</span> </a>
</li>
<li class="item btnGoogle">
<a class="js-btn btn gtm_socialShare" data-social="google" data-url="http://www.ehow.com/how_2042752_build-terrarium.html" data-title="How to Build a Terrarium"> <span class="social-icon elegant-icons"></span> <span class="action js-plusones">Share</span> </a>
</li>
</ul>
<div class="pagescore js-pagescore page-heading" data-score="true" data-url="http://www.ehow.com/how_2042752_build-terrarium.html"> <span class="headline3 num js-num"></span> <span class="icon"></span> <span class="msg">Found This Helpful</span> </div>
</div>
</header>
<div class="mod step">
<div class="stepContent mod">
<div class="content lead">
<p>Glass cloche terrariums are not only appealing to the eye, but they also preserve a bit of nature in your home and serve as a simple, yet beautiful, piece of art. Closed terrariums are easy to care for, as they retain much of their own moisture and provide a warm environment with a consistent level of humidity. You wont have to water the terrariums unless you see that the walls are not misting up. Small growing plants that dont require a lot of light work best such as succulents, ferns, moss, even orchids.</p>
</div>
<figure class="stepThumb"> <img src="http://img-aws.ehowcdn.com/640/cme/photography.prod.demandstudios.com/16149374-814f-40bc-baf3-ca20f149f0ba.jpg" alt="Glass cloche terrariums" title="Glass cloche terrariums" class="photo" data-credit="Lucy Akins " longdesc="http://s3.amazonaws.com/photography.prod.demandstudios.com/16149374-814f-40bc-baf3-ca20f149f0ba.jpg"/> </figure>
<figcaption class="small caption"> Glass cloche terrariums (Lucy Akins) </figcaption>
</div>
</div>
<div id="relatedContentUpper" class="RelatedContent Module" data-module="rcp_top">
<header class="">
<h3 class="head headline2 mg-2">Other People Are Reading</h3> </header>
<ul class="unstyled inline box box-inner people-reading mod FLC" data-type="more-relatedArticles">
<li class="fl item headline5 first">
<a class="gtm_otherPeopleReading" href="http://www.ehow.com/video_12263138_plant-terrarium-ideas.html"> <img src="http://img-aws.ehowcdn.com/105x70/viper/media/2b3830ca-4eb6-4bfd-a1cd-d430b3e28d59/jpeg/a7edbb0f-86a7-4c73-9c2d-f85f87b49efa_0.jpg" class="thumb fl"/> </a> <a class="title gtm_otherPeopleReading headline5" href="http://www.ehow.com/video_12263138_plant-terrarium-ideas.html">Plant Terrarium Ideas</a> </li>
<li class="fl item headline5 mid">
<a class="gtm_otherPeopleReading" href="http://www.ehow.com/how_4885344_build-terrarium-succulent-plants.html"> <img src="http://img-aws.ehowcdn.com/105x70/cme/photography.prod.demandstudios.com/619daaaa-7991-4fc6-9b62-dfeab8a285b4.jpg" class="thumb fl"/> </a> <a class="title gtm_otherPeopleReading headline5" href="http://www.ehow.com/how_4885344_build-terrarium-succulent-plants.html">How to Build a Terrarium With Succulent Plants</a> </li>
</ul>
</div>
<div class="mod step">
<div class="stepContent">
<div class="content"> <span class="headline2 head mg-1 block">What You'll Need:</span>
<ul class="markdown-ul">
<li>Cloche</li>
<li>Planter saucer, small shallow dish or desired platform</li>
<li>Floral foam oasis</li>
<li>Ruler </li>
<li>Spoon</li>
<li>Floral wire pins or paper clips</li>
<li>Small plants (from a florist or nursery)</li>
<li>Moss</li>
<li>Tweezers</li>
<li>Other small decorative items (optional)</li>
</ul>
</div>
</div>
</div>
<div class="mod step">
<div class="stepContent">
<div class="content"> <span class="headline2 head mg-1 block">Step 1</span>
<p>Measure the circumference of your cloche and cut the foam oasis about 3/4 inch (2 cm) smaller. Place the foam oasis into a container full of water and allow to soak until it sinks to the bottom. Dig out a hole on the oasis large enough to fit your plant, being careful not to pierce all the way through to the bottom.</p>
</div>
<figure class="stepThumb"> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/fc249ef6-4d27-41b4-8c21-15f7a8512b50.jpg" alt="Dig a hole in the oasis." class="photo" data-credit="Lucy Akins"/> </figure>
<figcaption class="small caption"> Dig a hole in the oasis. (Lucy Akins) </figcaption>
</div>
</div>
<div id="m1"></div>
<div class="mod Inline">
<div id="DMINSTR" type="adimpression" data-type="adTracking" name="&amp;adunit_id=ca-ehow_300x250&amp;ad_unit_type=CNT&amp;ad_unit_network=GAP&amp;revenue_basis=CPC&amp;ad_position=-&amp;revenue_tag=craft_other_diy">
<div id="GoogleAdsense300x250"></div>
<script>
dmjs.revenueTags.push('ca-ehow_300x250;craft_other_diy,text,suggested7');
googleAds.addAdUnit({
priority: 3,
adUnitId: 'GoogleAdsense300x250',
google_ad_channel: 'craft_other_diy,text,suggested7',
google_ad_client: 'ca-ehow_300x250',
google_page_url: 'http://www.ehow.com/how_2042752_build-terrarium.html',
google_max_num_ads: '1',
google_ad_type: 'text',
templates: {
text: '&lt;div class="GoogleTextAd GoogleAd300x250 ad-300"> \
&lt;p>&lt;a class="header" href="{!= google_info.feedback_url !}">Sponsored Links&lt;/a>&lt;/p> \
&lt;ul> \
{! for (var i = 0; i &lt; ads.length; i++) { !} \
&lt;li class="Ad"> \
&lt;a rel="nofollow" href="{!= ads[i].url !}" target="_blank" title="go to {!= clean(ads[i].visible_url) !}" class="title"> \
{!= ads[i].line1 !} \
&lt;/a> \
&lt;p class="copy">{!= ads[i].line2 !} {!= ads[i].line3 !}&lt;/p> \
&lt;a rel="nofollow" href="{!= ads[i].url !}" target="_blank" title="go to {!= clean(ads[i].visible_url) !}" class="baseurl url"> \
{!= ads[i].visible_url !} \
&lt;/a> \
&lt;/li> \
{! } !} \
&lt;/ul> \
&lt;/div>',
image: '&lt;div class="GoogleImageAd GoogleImageAdNew"> \
&lt;a class="header" href="{!= google_info.feedback_url !}">Sponsored Links&lt;/a> \
&lt;a href="{!= ads[0].url !}" title="go to {!= clean(ads[0].visible_url) !}" target="_blank" class="Ad" >\
&lt;img src="{!= ads[0].image_url !}" width="{!= ads[0].image_width !}" height="{!= ads[0].image_height !}" border="0" alt="" />\
&lt;/a> \
&lt;/div>',
flash: '&lt;div class="GoogleFlashAd GoogleFlashAdNew">\
&lt;a class="header" href="{!= google_info.feedback_url !}">Sponsored Links&lt;/a> \
&lt;div class="Ad">\
&lt;object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" width="{!= ads[0].image_width !}" height="{!= ads[0].image_height !}">&lt;param name="movie" value="{!= ads[0].image_url !}">&lt;param name="quality" value="high">&lt;param name="AllowScriptAccess" value="never">&lt;embed src="{!= ads[0].image_url !}" width="{!= ads[0].image_width !}" height="{!= ads[0].image_height !}" type="application/x-shockwave-flash" AllowScriptAccess="never" pluginspage="http://www.macromedia.com/go/getflashplayer">&lt;/embed>&lt;/object>\
&lt;/div>\
&lt;/div>'
},
google_image_size: '300x250'
});
</script>
</div>
</div>
<div class="mod step">
<div class="stepContent">
<div class="content"> <span class="headline2 head mg-1 block">Step 2</span>
<p>Insert your plant into the hole.</p>
</div>
<figure class="stepThumb"> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/aae11d4d-a4aa-4251-a4d9-41023ebf6d84.jpg" alt="Orchid in foam oasis" class="photo" data-credit="Lucy Akins"/> </figure>
<figcaption class="small caption"> Orchid in foam oasis (Lucy Akins) </figcaption>
</div>
</div>
<div class="mod step">
<div class="stepContent">
<div class="content"> <span class="headline2 head mg-1 block">Step 3</span>
<p>You can add various plants if you wish.</p>
</div>
<figure class="stepThumb"> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/7afdfa1e-da74-44b5-b89c-ca8123516272.jpg" alt="Various foliage" class="photo" data-credit="Lucy Akins"/> </figure>
<figcaption class="small caption"> Various foliage (Lucy Akins) </figcaption>
</div>
</div>
<div class="mod step">
<div class="stepContent">
<div class="content"> <span class="headline2 head mg-1 block">Step 4</span>
<p>Using floral pins, attach enough moss around the oasis to cover it.</p>
</div>
<figure class="stepThumb"> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/4f6612c0-316a-4c74-bb03-cb4e778f6d72.jpg" alt="Attach moss." class="photo" data-credit="Lucy Akins"/> </figure>
<figcaption class="small caption"> Attach moss. (Lucy Akins) </figcaption>
</div>
</div>
<div class="mod step">
<div class="stepContent">
<div class="content"> <span class="headline2 head mg-1 block">Step 5</span>
<p>Gently place the cloche over the oasis. The glass may push some of the moss upward, exposing some of the foam.</p>
</div>
<figure class="stepThumb"> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/eeb1e0b4-e573-40a3-8db1-2c76f0b13b84.jpg" alt="Place cloche over oasis." class="photo" data-credit="Lucy Akins"/> </figure>
<figcaption class="small caption"> Place cloche over oasis. (Lucy Akins) </figcaption>
</div>
</div>
<div class="mod step">
<div class="stepContent">
<div class="content"> <span class="headline2 head mg-1 block">Step 6</span>
<p>Simply pull down the moss with tweezers or insert more moss to fill in the empty spaces.</p>
</div>
<figure class="stepThumb"> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/812d4649-4152-4363-97c0-f181d02e709a.jpg" alt="Rearrange moss." class="photo" data-credit="Lucy Akins"/> </figure>
<figcaption class="small caption"> Rearrange moss. (Lucy Akins) </figcaption>
</div>
</div>
<div class="mod step">
<div class="stepContent">
<div class="content"> <span class="headline2 head mg-1 block">Step 7</span>
<p>You can use any platform you wish. In this case, a small saucer was used.</p>
</div>
<figure class="stepThumb"> <img src="http://img-aws.ehowcdn.com/default/cme/photography.prod.demandstudios.com/0cb3988c-9318-47d6-bc9c-c798da1ede72.jpg" alt="Place cloche on a platform to sit on." class="photo" data-credit="Lucy Akins"/> </figure>
<figcaption class="small caption"> Place cloche on a platform to sit on. (Lucy Akins) </figcaption>
</div>
</div>
<div class="mod step">
<div class="stepContent">
<div class="content"> <span class="headline2 head mg-1 block">Step 8</span>
<p>This particular terrarium rests on a planter saucer and features a small white pumpkin.</p>
</div>
<figure class="stepThumb"> <img src="http://img-aws.ehowcdn.com/640/cme/photography.prod.demandstudios.com/e3e18f0b-ab2c-4ffb-9988-a1ea63faef8b.jpg" alt="Cloche placed on a terracotta saucer" class="photo" data-credit="Lucy Akins"/> </figure>
<figcaption class="small caption"> Cloche placed on a terracotta saucer (Lucy Akins) </figcaption>
</div>
</div>
<div class="mod step">
<div class="stepContent">
<div class="content"> <span class="headline2 head mg-1 block">Step 9</span>
<p>This particular terrarium was placed on a wood slice and a little toy squirrel was placed inside to add a little whimsy.</p>
</div>
<figure class="stepThumb"> <img src="http://img-aws.ehowcdn.com/640/cme/photography.prod.demandstudios.com/2cd79f8d-0d16-4573-8861-e47fb74b0638.jpg" alt="Placed on a wooden slice" class="photo" data-credit="Lucy Akins"/> </figure>
<figcaption class="small caption"> Placed on a wooden slice (Lucy Akins) </figcaption>
</div>
</div>
<div class="mod step">
<div class="stepContent">
<div class="content"> <span class="headline2 head mg-1 block">Finished Terrarium</span>
<p>Displayed alone or in a group, these pretty arrangements allow you to add a little nature to your decor or tablescape.</p>
</div>
<figure class="stepThumb"> <img src="http://img-aws.ehowcdn.com/640/cme/photography.prod.demandstudios.com/78670312-8636-4c42-a75c-3029f7aa6c73.jpg" alt="Cloche terrarium" class="photo" data-credit="Lucy Akins"/> </figure>
<figcaption class="small caption"> Cloche terrarium (Lucy Akins) </figcaption>
</div>
</div>
<div id="m2"></div>
<div id="DMINSTR" type="adimpression" data-type="adTracking" name="&amp;adunit_id=ca-ehow_336x280&amp;ad_unit_type=CNT&amp;ad_unit_network=GAP&amp;revenue_basis=CPC&amp;ad_position=-&amp;revenue_tag=craft_other_diy" data-module="google-ad-336x280">
<div id="GoogleAdsense336x280" class="mod"></div>
<script>
dmjs.revenueTags.push('ca-ehow_336x280;craft_other_diy,text,suggested7');
googleAds.addAdUnit({
priority: 2,
adUnitId: 'GoogleAdsense336x280',
google_ad_channel: 'craft_other_diy,text,suggested7',
google_ad_client: 'ca-ehow_336x280',
google_page_url: 'http://www.ehow.com/how_2042752_build-terrarium.html',
google_max_num_ads: '4',
google_ad_type: 'text',
google_hints: '',
templates: {
text: '&lt;div class="GoogleTextAd GoogleTextAdNew">\
&lt;a class="header" href="{!= google_info.feedback_url !}">Sponsored Links&lt;/a>\
&lt;ul class="opNew336 evenColorItems">\
{! for (var i = 0; i &lt; ads.length; i++) { !}\
&lt;li class="item">\
&lt;div class="term">\
&lt;a rel="nofollow" target="_blank" href="{!= ads[i].url !}" title="go to {!= clean(ads[i].visible_url) !}" class="title">\
&lt;span>{!= ads[i].line1 !}&lt;/span>\
&lt;/a>\
&lt;/div>\
&lt;div class="Ad">\
&lt;p class="copy">{!= ads[i].line2 !}&lt;/p>\
&lt;p class="copy">{!= ads[i].line3 !}&lt;/p>\
&lt;a rel="nofollow" target="_blank" href="{!= ads[i].url !}" title="go to {!= clean(ads[i].visible_url) !}" class="baseurl url">\
{!= ads[i].visible_url !}\
&lt;/a>\
&lt;/div>\
&lt;/li>\
{! } !}\
&lt;/ul>\
&lt;/div>',
image: '&lt;div class="GoogleImageAd GoogleImageAdNew"> \
&lt;a class="header" href="{!= google_info.feedback_url !}">Sponsored Links&lt;/a> \
&lt;a href="{!= ads[0].url !}" title="go to {!= clean(ads[0].visible_url) !}" target="_blank" class="Ad" >\
&lt;img src="{!= ads[0].image_url !}" width="{!= ads[0].image_width !}" height="{!= ads[0].image_height !}" border="0" alt="" />\
&lt;/a> \
&lt;/div>',
flash: '&lt;div class="GoogleFlashAd GoogleFlashAdNew">\
&lt;a class="header" href="{!= google_info.feedback_url !}">Sponsored Links&lt;/a> \
&lt;div class="Ad">\
&lt;object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" width="{!= ads[0].image_width !}" height="{!= ads[0].image_height !}">&lt;param name="movie" value="{!= ads[0].image_url !}">&lt;param name="quality" value="high">&lt;param name="AllowScriptAccess" value="never">&lt;embed src="{!= ads[0].image_url !}" width="{!= ads[0].image_width !}" height="{!= ads[0].image_height !}" type="application/x-shockwave-flash" AllowScriptAccess="never" pluginspage="http://www.macromedia.com/go/getflashplayer">&lt;/embed>&lt;/object>\
&lt;/div>\
&lt;/div>'
},
google_image_size: '336x280'
});
AdblockPlus.detect(function(usesABP) {
if (usesABP) {
googleAds.overrideAd('GoogleAdsense336x280', {
google_ad_channel: 'craft_other_diy,text,suggested7,Art6'
});
}
});
</script>
</div>
<div class="RelatedSearches FLC mod" id="RelatedSearches" data-module="related-searches">
<div class="headline2 head heading">Related Searches</div>
</div>
<div class="community mod" style="float:left;width: 610px;">
<div id="_ehow-com-injection" data-article-id="2042752" data-cat-id="5352" data-scat-id="5426" data-sscat-id="5429"></div>
<script id="_ehow-com-article" type="text/javascript">
dmjs.domready(function() {
if (!(dmjs.platform.isTablet())) {
var ehowCom = document.createElement('script');
ehowCom.type = 'text/javascript';
ehowCom.async = true;
ehowCom.src = 'https://community.ehow.com/widget/ehow/community.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ehowCom, s);
}
});
</script>
</div>
<aside class="mod related clearfix" data-module="rcp_bottom">
<h3 class="head headline2 mg-2">More Like This</h3>
<ul class="unstyled">
<li class="item fl ">
<a href="http://www.ehow.com/how_4431062_convert-aquarium-terrarium.html"> <img src="http://img-aws.ehowcdn.com/200x133/cme/photography.prod.demandstudios.com/90f9f3d7-6e4f-4be8-b379-3d994ccbf69c.jpg" class="img" alt=""/>
<p class="headline6 mg-0">How to Convert an Aquarium to a Terrarium</p>
</a>
</li>
<li class="item fl even">
<a href="http://www.ehow.com/video_12263138_plant-terrarium-ideas.html"> <img src="http://img-aws.ehowcdn.com/200x133/viper/media/2b3830ca-4eb6-4bfd-a1cd-d430b3e28d59/jpeg/a7edbb0f-86a7-4c73-9c2d-f85f87b49efa_0.jpg" class="img" alt=""/>
<p class="headline6 mg-0">Plant Terrarium Ideas</p>
</a>
</li>
<li class="item fl ">
<a href="http://www.ehow.com/video_9374_make-terrarium.html"> <img src="http://img-aws.ehowcdn.com/200x133/ehow/images/a00/09/4u/make-terrarium-800x800.jpg" class="img" alt=""/>
<p class="headline6 mg-0">How to Make a Terrarium</p>
</a>
</li>
</ul>
</aside>
<figure id="Comments" class="mod FLC" data-module="comments">
<div data-id="fb_comments"></div>
</figure>
<section id="FeaturedTombstone" class="mod" data-module="rcp_tombstone">
<h2 class="headline2 head mg-2">Featured</h2>
<div class="featured FLC">
<section class="Alpha item" data-cme-module="0">
<a class="target link-accent gtm_featuredArticles" href="http://www.ehow.com/how_2056495_make-ballet-tutu.html"> <img src="http://img-aws.ehowcdn.com/200x133/cme/photography.prod.demandstudios.com/7f06be69-3650-4e94-88d4-2289c1a5c6a2.jpg" class="photo" alt=""/> <span class="subtitle">Read Article</span>
<div class="title headline6">How to Make a Ballet Tutu</div>
</a>
</section>
<section class=" item" data-cme-module="1">
<a class="target link-accent gtm_featuredArticles" href="http://www.ehow.com/how_7687715_paint-tulip-watercolor.html"> <img src="http://img-aws.ehowcdn.com/200x133/cme/uploadedimages.demandmedia/tulips-1.jpg" class="photo" alt=""/> <span class="subtitle">Read Article</span>
<div class="title headline6">How to Paint a Tulip in Watercolor</div>
</a>
</section>
<section class="Omega item" data-cme-module="2">
<a class="target link-accent gtm_featuredArticles" href="http://www.ehow.com/info_12340422_kids-kitchen-slipcover.html"> <img src="http://img-aws.ehowcdn.com/200x133/cme/uploadedimages.demandmedia/kitchen-1.jpg" class="photo" alt=""/> <span class="subtitle">Read Article</span>
<div class="title headline6">Kids Kitchen Slipcover</div>
</a>
</section>
</div>
</section>
</div>
<div class="col-rail">
<!-- gpt slot 300x250_300x600 -->
<div class="AdUnit mod" data-module="gpt-ad-300x250_300x600" id="300x250_300x600">
<script type="text/javascript">
googletag.cmd.push(function() {
googletag.display('300x250_300x600');
});
</script>
</div>
<!-- gpt slot 300x100 -->
<div class="mod hide" data-module="radlinks">
<h3 class="headline3 head mg-2">Related Ads</h3>
<div id="DMINSTR" type="rlimpression" data-type="adTracking" name="&amp;adunit_id=ca-ehow-radlinks_js&amp;ad_unit_type=LNK&amp;ad_unit_network=GAP&amp;revenue_basis=CPC&amp;ad_position=-&amp;revenue_tag=craft_other_diy">
<div id="GoogleRelatedAds"></div>
<script>
dmjs.revenueTags.push('ca-ehow-radlinks_js;craft_other_diy,text,suggested7');
googleAds.addAdUnit({
priority: 1,
adUnitId: 'GoogleRelatedAds',
google_ad_channel: 'craft_other_diy,text,suggested7',
google_ad_client: 'ca-ehow-radlinks_js',
google_page_url: 'http://www.ehow.com/how_2042752_build-terrarium.html',
google_max_num_ads: '0',
google_num_radlinks: '6',
google_max_radlink_len: '27',
metaData: {
"contentid": "2042752",
"studioid": "f941e4cd-a49e-4948-b244-677c15ff8b7f",
"category": "Crafts",
"subcategory": "Other DIY Crafts",
"skin": "crafts"
}
});
</script>
</div>
</div>
<div class="RelatedSearches FLC mod" id="RelatedSearches" data-module="related-searches-right">
<div class="headline3 head mg-2">Related Searches</div>
</div>
<section class="mod box slideshow" data-module="rcp_slideshow_module">
<a href="http://www.ehow.com/how_16357_make-paper-sunflowers.html"> <img src="http://img-aws.ehowcdn.com/300x200/cme/uploadedimages.demandmedia/sunflowers-1.jpg" alt="" title="" class="photo" longdesc="http://s3.amazonaws.com/uploadedimages.demandmedia/sunflowers-1.jpg"/> <span class="arrow arrow-left" data-icon="4"></span> <span class="arrow arrow-right" data-icon="5"></span> <span class="subtitle">
Read Article </span> </a>
<a href="http://www.ehow.com/how_16357_make-paper-sunflowers.html">
<h3 class="headline4 title">
How to Make Paper Sunflowers </h3> </a>
</section>
<ul class="media-list mod unstyled " data-module="rcp_right_rail">
<h2 class="headline3 head mg-2 title">You May Like</h2>
<li class="media headline6">
<a href="http://www.ehow.com/how_4431062_convert-aquarium-terrarium.html" class="fl gtm_youMayLike"> <img src="http://img-aws.ehowcdn.com/150X100/cme/photography.prod.demandstudios.com/90f9f3d7-6e4f-4be8-b379-3d994ccbf69c.jpg" alt="" class="media-object"/>
<div class="pagescore js-pagescore small" data-score="true" data-url="http://www.ehow.com/how_4431062_convert-aquarium-terrarium.html"> <span class=" num js-num"></span> <span class="icon"></span> </div>
</a>
<div class="media-body"> <a href="http://www.ehow.com/how_4431062_convert-aquarium-terrarium.html" class="gtm_youMayLike">How to Convert an Aquarium to a Terrarium</a> </div>
</li>
<li class="media headline6">
<a href="http://www.ehow.com/video_12263137_build-orchid-terrarium.html" class="fl gtm_youMayLike"> <img src="http://img-aws.ehowcdn.com/150X100/viper/media/2b3830ca-4eb6-4bfd-a1cd-d430b3e28d59/jpeg/47b02568-2cde-44f4-a39c-494294e50ccd_1.jpg" alt="" class="media-object"/>
<div class="pagescore js-pagescore small" data-score="true" data-url="http://www.ehow.com/video_12263137_build-orchid-terrarium.html"> <span class=" num js-num"></span> <span class="icon"></span> </div>
</a>
<div class="media-body"> <a href="http://www.ehow.com/video_12263137_build-orchid-terrarium.html" class="gtm_youMayLike">How to Build an Orchid Terrarium</a> </div>
</li>
<li class="media headline6">
<a href="http://www.ehow.com/video_9374_make-terrarium.html" class="fl gtm_youMayLike"> <img src="http://img-aws.ehowcdn.com/150X100/ehow/images/a00/09/4u/make-terrarium-800x800.jpg" alt="" class="media-object"/>
<div class="pagescore js-pagescore small" data-score="true" data-url="http://www.ehow.com/video_9374_make-terrarium.html"> <span class=" num js-num"></span> <span class="icon"></span> </div>
</a>
<div class="media-body"> <a href="http://www.ehow.com/video_9374_make-terrarium.html" class="gtm_youMayLike">How to Make a Terrarium</a> </div>
</li>
<li class="media headline6">
<a href="http://www.ehow.com/video_12263133_group-plants-together-terrarium.html" class="fl gtm_youMayLike"> <img src="http://img-aws.ehowcdn.com/150X100/viper/media/2b3830ca-4eb6-4bfd-a1cd-d430b3e28d59/jpeg/dc66dd37-78b6-4494-a8db-7bbe697cedda_0.jpg" alt="" class="media-object"/>
<div class="pagescore js-pagescore small" data-score="true" data-url="http://www.ehow.com/video_12263133_group-plants-together-terrarium.html"> <span class=" num js-num"></span> <span class="icon"></span> </div>
</a>
<div class="media-body"> <a href="http://www.ehow.com/video_12263133_group-plants-together-terrarium.html" class="gtm_youMayLike">How to Group Plants Together in a Terrarium</a> </div>
</li>
<li class="media headline6">
<a href="http://www.ehow.com/video_12263135_build-tropical-terrarium.html" class="fl gtm_youMayLike"> <img src="http://img-aws.ehowcdn.com/150X100/viper/media/2b3830ca-4eb6-4bfd-a1cd-d430b3e28d59/jpeg/70ebb809-634d-41df-9fff-9b183f81e9e0_1.jpg" alt="" class="media-object"/>
<div class="pagescore js-pagescore small" data-score="true" data-url="http://www.ehow.com/video_12263135_build-tropical-terrarium.html"> <span class=" num js-num"></span> <span class="icon"></span> </div>
</a>
<div class="media-body"> <a href="http://www.ehow.com/video_12263135_build-tropical-terrarium.html" class="gtm_youMayLike">How to Build a Tropical Terrarium</a> </div>
</li>
<li class="media headline6">
<a href="http://www.ehow.com/how_7853040_diy-own-living-room-furniture.html" class="fl gtm_youMayLike"> <img src="http://img-aws.ehowcdn.com/150X100/cme/photography.prod.demandstudios.com/2ecfca5a-1c35-4fbe-9ff5-8f5bd8129d97.jpg" alt="" class="media-object"/>
<div class="pagescore js-pagescore small" data-score="true" data-url="http://www.ehow.com/how_7853040_diy-own-living-room-furniture.html"> <span class=" num js-num"></span> <span class="icon"></span> </div>
</a>
<div class="media-body"> <a href="http://www.ehow.com/how_7853040_diy-own-living-room-furniture.html" class="gtm_youMayLike">DIY Build Your Own Living Room Furniture</a> </div>
</li>
<li class="media headline6">
<a href="http://www.ehow.com/slideshow_12333243_edible-arrangements-make-own-easter-candy-terrariums.html" class="fl gtm_youMayLike"> <img src="http://img-aws.ehowcdn.com/150X100/ds-cdn-write/upload/image/39/4D/34B73D38-EC9F-4415-A740-23DCF2F94D39/34B73D38-EC9F-4415-A740-23DCF2F94D39.jpg" alt="" class="media-object"/>
<div class="pagescore js-pagescore small" data-score="true" data-url="http://www.ehow.com/slideshow_12333243_edible-arrangements-make-own-easter-candy-terrariums.html"> <span class=" num js-num"></span> <span class="icon"></span> </div>
</a>
<div class="media-body"> <a href="http://www.ehow.com/slideshow_12333243_edible-arrangements-make-own-easter-candy-terrariums.html" class="gtm_youMayLike">Edible Arrangements: Make Your Own Easter Candy Terrariums</a> </div>
</li>
<li class="media headline6">
<a href="http://www.ehow.com/ehow-mom/blog/3-ingredient-easter-bark/" class="fl gtm_youMayLike"> <img src="http://img-aws.ehowcdn.com/150X100/ehow-mom-blog-us/files/2015/03/easterbark0.jpg" alt="" class="media-object"/>
<div class="pagescore js-pagescore small" data-score="true" data-url="http://www.ehow.com/ehow-mom/blog/3-ingredient-easter-bark/"> <span class=" num js-num"></span> <span class="icon"></span> </div>
</a>
<div class="media-body"> <a href="http://www.ehow.com/ehow-mom/blog/3-ingredient-easter-bark/" class="gtm_youMayLike">3-Ingredient Easter Bark</a> </div>
</li>
</ul>
</div>
</section>
<script src="http://v5-static.ehowcdn.com/content/compressed/module-jquery-764eded0.js"></script>
<footer id="Footer" data-section="footer">
<div class="inner-container">
<div class="details clearfix">
<div class="identity"> <a href="http://www.demandmedia.com/" class="logo">demandmedia.com</a>
<p class="copyright">© 1999-2015 Demand Media, Inc.</p>
</div>
<nav xmlns="http://www.w3.org/1999/html">
<ul class="listing">
<li>
<ul>
<li class="item row1"> <a href="http://www.ehow.com/about-us.html" class="title">About eHow</a> </li>
<li class="item row1"> <a href="http://www.ehow.co.uk/" class="title">eHow UK</a> </li>
<li class="item row1"> <a href="http://www.ehowenespanol.com/" class="title">eHow en Español</a> </li>
<li class="item row1"> <a href="http://www.ehow.com.br/" class="title">eHow Brasil</a> </li>
<li class="item row1"> <a href="http://www.ehow.de/" class="title">eHow Deutschland</a> </li>
<li class="item row1"> <a href="http://www.ehowhelp.com/hc/en-us" class="title">Contact Us</a> </li>
</ul>
</li>
<li>
<ul>
<li class="item row2"> <a href="http://www.ehow.com/blog/" class="title">eHow Blog</a> </li>
<li class="item row2"> <a href="http://www.ehow.com/how-to.html" class="title">How to by Topic</a> </li>
<li class="item row2"> <a href="http://www.ehow.com/videos.html" class="title">How to Videos</a> </li>
</ul>
</li>
<li>
<ul>
<li class="item row3"> <a href="http://www.ehow.com/terms-of-use.html" class="title">Terms of Use</a> </li>
<li class="item row3"> <a href="http://www.ehow.com/privacy.html" class="title">Privacy Policy</a> </li>
<li class="item row3"> <a href="http://www.ehow.com/terms-of-use.html#copyright/" class="title">Report Copyright</a> </li>
<li class="item row3">
<a id="_bapw-link" class="ad-choice" style="cursor:pointer !important"><img id="_bapw-icon" style="display:inline !important;vertical-align:middle !important"/> <span style="vertical-align:middle !important">Ad Choices</span></a> <abbr>en-US</abbr> </li>
</ul>
</li>
</ul>
</nav>
<div class="socialConnect fr">
<p class="headline4 heading head">Connect with us:</p>
<ul>
<li class="source"><a class="icon elegant-icons gtm_social" href="http://www.pinterest.com/ehow/" target="_blank"></a></li>
<li class="source"><a class="icon elegant-icons gtm_social" href="https://www.facebook.com/eHow" target="_blank"></a></li>
<li class="source"><a class="icon elegant-icons gtm_social" href="https://twitter.com/ehow" target="_blank"></a></li>
<li class="source"><a class="icon elegant-icons gtm_social" href="https://plus.google.com/+howto/posts" target="_blank"></a></li>
<li class="source last"><a class="icon elegant-icons gtm_social" href="http://instagram.com/ehow_team" target="_blank"></a></li>
</ul>
</div>
</div>
</div>
<div class="extras">
<div class="comscore comscore-eh">
<div class="category5352 eh-cat"></div>
</div>
</div>
</footer>
<!-- BEGIN dmtracker -->
<noscript><img src="http://vs.dmtracker.com/images/zig.gif?Log=1" alt="zig" /></noscript>
<div id="dmOmni"></div>
<!-- END dmtracker -->
<noscript><img src="http://b.scorecardresearch.com/b?c1=2&c2=6036385&c3=&c7=http%3A%2F%2Fwww.ehow.com%2Fhow_2042752_build-terrarium.html%3Fcomscorekw%3DeHow_Hobbies_Games_and_Toys&cv=2.0&cj=1" /></noscript>
<script>
var dmjs = dmjs || {};
dmjs.global = dmjs.global || {};
dmjs.global.comscore = {
"c1": "2",
"c2": "6036385",
"c3": "",
"options": {
"url_append": "comscorekw=eHow_Hobbies_Games_and_Toys"
}
};
dmjs.global.dm_tracker = {
"domain": "vs.dmtracker.com",
"url": "bit.ehow.com",
"bit": "bit"
};
dmjs.global.fb_comment = {
"num_posts": 5,
"width": 620,
"mobile": false,
"href": "http:\/\/www.ehow.com\/how_2042752_build-terrarium.html",
"selector": "[data-id=\"fb_comments\"]"
};
dmjs.global.hnh = {
"url": "http:\/\/services.ehowcdn.com\/widgets\/generated\/hnh.min.js"
};
</script>
<script src="http://dynamic01.ehowcdn.com/services/modules/js/hnh,evidon,rev_tag,dm_track,comscore,common_deferred,cache_buster/ba9c0085/" defer="defer"></script>
<!--[if lt IE 9]>
<script src="http://v5-static.ehowcdn.com/content/compressed/common_footer-266c5d77.js" ></script>
<![endif]-->
<script src="http://v5-static.ehowcdn.com/content/compressed/inline-81314781.js" defer="defer"></script>
<script>
dmjs.domready(function() {
new OmnitureLinkSet($('[data-module="rcp_top"]'), "TLS101", "default_IL");
});
</script>
<script>
dmjs.domready(function() {
new OmnitureLinkSet($('[data-module="rcp_bottom"]'), "TLS103", "default_B");
});
</script>
<script>
dmjs.domready(function() {
new OmnitureLinkSet($('[data-module="rcp_tombstone"]'), "LS103", "default_TOMB");
});
</script>
<script>
dmjs.domready(function() {
new OmnitureLinkSet($('[data-module="rcp_slideshow_module"]'), "TLS106", "default_SS");
});
</script>
<script>
dmjs.domready(function() {
new OmnitureLinkSet($('[data-module="rcp_right_rail"]'), "TLS102", "default_R3");
});
</script>
<script>
googleAds.defaults.google_adtest = 'off';
googleAds.defaults.google_page_url = 'http://www.ehow.com/how_2042752_build-terrarium.html';
googleAds.render();
</script>
<script defer="" src="//cdn.optimizely.com/js/9718688.js"></script>
<script>
dmjs.addEvent(window, 'load', function() {
dmjs.fireEvents();
});
</script>
<script defer="" src="http://static.ehownowcdn.com/widget/ehow/auth.js"></script>
<div id="fb-root"></div>
<div class="view-mobile"></div>
</body>
</html>

@ -1,8 +0,0 @@
{
"title": "How to Throw a Graduation Party on a Budget | eHow",
"byline": "Gina Roberts-Grey",
"dir": null,
"excerpt": "Graduation parties are a great way to commemorate the years of hard work teens and college co-eds devote to education. Theyre also costly for mom and dad.The average cost of a graduation party in 2013 was a whopping $1,200, according to Graduationparty.com; $700 of that was allocated for food. However that budget was based on Midwestern...",
"siteName": "eHow",
"readerable": true
}

@ -1,140 +0,0 @@
<div id="readability-page-1" class="page">
<div data-type="AuthorProfile">
<div>
<p><a id="img-follow-tip" href="http://fakehost/contributor/gina_robertsgrey/" target="_top">
<img src="http://img-aws.ehowcdn.com/60x60/cme/cme_public_images/www_demandstudios_com/sitelife.studiod.com/ver1.0/Content/images/store/9/2/d9dd6f61-b183-4893-927f-5b540e45be91.Small.jpg" data-failover="//img-aws.ehowcdn.com/60x60/ehow-cdn-assets/test15/media/images/authors/missing-author-image.png" onerror="var failover = this.getAttribute(&apos;data-failover&apos;);
if (failover) failover = failover.replace(/^https?:/,&apos;&apos;);
var src = this.src ? this.src.replace(/^https?:/,&apos;&apos;) : &apos;&apos;;
if (src != failover){
this.src = failover;
}" /> </a></p>
</div>
<p><time datetime="2016-09-14T07:07:00-04:00" itemprop="dateModified">Last updated September 14, 2016</time>
</p>
</div>
<div>
<article data-type="article">
<div>
<div>
<p>Graduation parties are a great way to commemorate the years of hard work teens and college co-eds devote to education. Theyre also costly for mom and dad.</p>
<p>The average cost of a graduation party in 2013 was a whopping $1,200, according to Graduationparty.com; $700 of that was allocated for food. However that budget was based on Midwestern statistics, and parties in urban areas like New York City are thought to have a much higher price tag.</p>
<p>Thankfully, there are plenty of creative ways to trim a little grad party fat without sacrificing any of the fun or celebratory spirit.</p>
</div>
<figure>
<img src="http://img-aws.ehowcdn.com/640/cme/cme_public_images/www_ehow_com/cdn-write.demandstudios.com/upload/image/2F/86/5547EF62-EAF5-4256-945D-0496F61C862F/5547EF62-EAF5-4256-945D-0496F61C862F.jpg" alt="Graduation" title="Graduation" data-credit="Mike Watson Images/Moodboard/Getty " longdesc="http://s3.amazonaws.com/cme_public_images/www_ehow_com/cdn-write.demandstudios.com/upload/image/2F/86/5547EF62-EAF5-4256-945D-0496F61C862F/5547EF62-EAF5-4256-945D-0496F61C862F.jpg" data-pin-ehow-hover="true" data-pin-no-hover="true" />
</figure>
<figcaption class="caption"> (Mike Watson Images/Moodboard/Getty) </figcaption>
</div>
<span>
<span>
<div>
<p><span>
<p>Parties hosted at restaurants, clubhouses and country clubs eliminate the need to spend hours cleaning up once party guests have gone home. But that convenience comes with a price tag. A country club may charge as much as $2,000 for room rental and restaurant food and beverage will almost always cost more than food prepped and served at home.</p>
</span> </p>
<figure>
<img src="http://img-aws.ehowcdn.com/640/cme/cme_public_images/www_ehow_com/cdn-write.demandstudios.com/upload/image/FE/CB/121569D2-6984-4B2F-83C4-9D2D9A27CBFE/121569D2-6984-4B2F-83C4-9D2D9A27CBFE.jpg" alt="Save money hosting the party at home." data-credit="Thomas Jackson/Digital Vision/Getty Images" data-pin-ehow-hover="true" data-pin-no-hover="true" />
</figure>
<figcaption class="caption"> Thomas Jackson/Digital Vision/Getty Images </figcaption>
</div>
</span>
</span>
<span>
<span>
<div>
<p><span>
<p>Instead of hiring a DJ, use your iPod or Smartphone to spin the tunes. Both easily hook up to most speakers or mp3 compatible docks to play music from your music library. Or download Pandora, the free online radio app, and play hours of music for free.</p>
<p>Personalize the music with a playlist of the grads favorite songs or songs that were big hits during his or her years in school.</p>
</span> </p>
<figure>
<img src="http://img-aws.ehowcdn.com/640/cme/cme_public_images/www_ehow_com/cdn-write.demandstudios.com/upload/image/DF/FC/A05B0252-BD73-4BC7-A09A-96F0A504FCDF/A05B0252-BD73-4BC7-A09A-96F0A504FCDF.jpg" alt="Online radio can take the place of a hired DJ." data-credit="Spencer Platt/Getty Images News/Getty Images" data-pin-ehow-hover="true" data-pin-no-hover="true" />
</figure>
<figcaption class="caption"> Spencer Platt/Getty Images News/Getty Images </figcaption>
</div>
</span>
</span>
<span>
<span>
<div>
<p><span>
<p>Avoid canned drinks, which guests often open, but don't finish. Serve pitchers of tap water with lemon and cucumber slices or sliced strawberries for an interesting and refreshing flavor. Opt for punches and non-alcoholic drinks for high school graduates that allow guests to dole out the exact amount they want to drink.</p>
</span> </p>
<figure>
<img src="http://img-aws.ehowcdn.com/640/cme/cme_public_images/www_ehow_com/cdn-write.demandstudios.com/upload/image/EB/DB/8A04CCA7-3255-4225-B59A-C41441F8DBEB/8A04CCA7-3255-4225-B59A-C41441F8DBEB.jpg" alt="Serve drinks in pitchers, not in cans." data-credit="evgenyb/iStock/Getty Images" data-pin-ehow-hover="true" data-pin-no-hover="true" />
</figure>
<figcaption class="caption"> evgenyb/iStock/Getty Images </figcaption>
</div>
</span>
</span>
<span>
<span>
<div>
<p><span>
<p>Instead of inviting everyone you and the graduate know or ever knew, scale back the guest list. Forgo inviting guests that you or your grad haven't seen for eons. There is no reason to provide provisions for people who are essentially out of your lives. Sticking to a small, but personal, guest list allows more time to mingle with loved ones during the party, too.</p>
</span> </p>
<figure>
<img src="http://img-aws.ehowcdn.com/640/cme/cme_public_images/www_ehow_com/cdn-write.demandstudios.com/upload/image/94/10/08035476-0167-4A03-AADC-13A7E7AA1094/08035476-0167-4A03-AADC-13A7E7AA1094.jpg" alt="Limit guests to those close to the graduate." data-credit="Kane Skennar/Photodisc/Getty Images" data-pin-ehow-hover="true" data-pin-no-hover="true" />
</figure>
<figcaption class="caption"> Kane Skennar/Photodisc/Getty Images </figcaption>
</div>
</span>
</span>
<span>
<span>
<div>
<p><span>
<p>See if your grad and his best friend, girlfriend or close family member would consider hosting a joint party. You can split some of the expenses, especially when the two graduates share mutual friends. You'll also have another parent to bounce ideas off of and to help you stick to your budget when you're tempted to splurge.</p>
</span> </p>
<figure>
<img src="http://img-aws.ehowcdn.com/640/cme/cme_public_images/www_ehow_com/cdn-write.demandstudios.com/upload/image/06/49/4AD62696-FC95-4DA2-8351-42740C7B4906/4AD62696-FC95-4DA2-8351-42740C7B4906.jpg" alt="Throw a joint bash for big savings." data-credit="Mike Watson Images/Moodboard/Getty" data-pin-ehow-hover="true" data-pin-no-hover="true" />
</figure>
<figcaption class="caption"> Mike Watson Images/Moodboard/Getty </figcaption>
</div>
</span>
</span>
<span>
<span>
<div>
<p><span>
<p>Skip carving stations of prime rib and jumbo shrimp as appetizers, especially for high school graduation parties. Instead, serve some of the graduate's favorite side dishes that are cost effective, like a big pot of spaghetti with breadsticks. Opt for easy and simple food such as pizza, finger food and mini appetizers. </p>
<p>Avoid pre-packaged foods and pre-made deli platters. These can be quite costly. Instead, make your own cheese and deli platters for less than half the cost of pre-made.</p>
</span> </p>
<figure>
<img src="http://img-aws.ehowcdn.com/640/cme/cme_public_images/www_ehow_com/cdn-write.demandstudios.com/upload/image/D0/51/B6AED06C-5E19-4A26-9AAD-0E175F6251D0/B6AED06C-5E19-4A26-9AAD-0E175F6251D0.jpg" alt="Cost effective appetizers are just as satisfying as pre-made deli platters." data-credit="Mark Stout/iStock/Getty Images" data-pin-ehow-hover="true" data-pin-no-hover="true" />
</figure>
<figcaption class="caption"> Mark Stout/iStock/Getty Images </figcaption>
</div>
</span>
</span>
<span>
<span>
<div>
<p><span>
<p>Instead of an evening dinner party, host a grad lunch or all appetizers party. Brunch and lunch fare or finger food costs less than dinner. Guests also tend to consume less alcohol in the middle of the day, which keeps cost down.</p>
</span> </p>
<figure>
<img src="http://img-aws.ehowcdn.com/640/cme/cme_public_images/www_ehow_com/cdn-write.demandstudios.com/upload/image/35/B4/DD5FD05A-B631-4AFE-BC8F-FDACAD1EB435/DD5FD05A-B631-4AFE-BC8F-FDACAD1EB435.jpg" alt="A brunch gathering will cost less than a dinner party." data-credit="Mark Stout/iStock/Getty Images" data-pin-ehow-hover="true" data-pin-no-hover="true" />
</figure>
<figcaption class="caption"> Mark Stout/iStock/Getty Images </figcaption>
</div>
</span>
</span>
<span>
<span>
<div>
<p><span>
<p>Decorate your party in the graduate's current school colors or the colors of the school he or she will be headed to next. Décor that is not specifically graduation-themed may cost a bit less, and any leftovers can be re-used for future parties, picnics and events.</p>
</span> </p>
<figure>
<img src="http://img-aws.ehowcdn.com/640/cme/cme_public_images/www_ehow_com/cdn-write.demandstudios.com/upload/image/A1/FA/2C368B34-8F6A-45F6-9DFC-0B0C4E33FAA1/2C368B34-8F6A-45F6-9DFC-0B0C4E33FAA1.jpg" alt="Theme the party by color without graduation-specific decor." data-credit="jethuynh/iStock/Getty Images" data-pin-ehow-hover="true" data-pin-no-hover="true" />
</figure>
<figcaption class="caption"> jethuynh/iStock/Getty Images </figcaption>
</div>
</span>
</span>
<h2>
<a target="_blank" href="https://www.google.com/adsense/support/bin/request.py?contact=abg_afc&amp;url=http://ehow.com/&amp;hl=en&amp;client=ehow&amp;gl=US">Related Searches</a>
</h2>
<p>Promoted By Zergnet</p>
</article>
</div>
</div>

File diff suppressed because it is too large Load Diff

@ -1,8 +1,6 @@
{
"title": "Embedded videos test",
"byline": null,
"dir": null,
"excerpt": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\n quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\n consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse\n cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non\n proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"siteName": null,
"readerable": false
}

@ -1,16 +1,37 @@
<div id="readability-page-1" class="page">
<article>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<h2>Videos</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<h2>Videos</h2>
<p>At root</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/LtOGa5M8AuU" frameborder="0" allowfullscreen=""></iframe>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/LtOGa5M8AuU" frameborder="0" allowfullscreen=""></iframe>
<iframe src="https://player.vimeo.com/video/32246206?color=ffffff+title=0+byline=0+portrait=0" width="500" height="281" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/LtOGa5M8AuU"
frameborder="0" allowfullscreen=""></iframe>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/LtOGa5M8AuU"
frameborder="0" allowfullscreen=""></iframe>
<iframe src="https://player.vimeo.com/video/32246206?color=ffffff+title=0+byline=0+portrait=0"
width="500" height="281" frameborder="0" webkitallowfullscreen="" mozallowfullscreen=""
allowfullscreen=""></iframe>
<p>In a paragraph</p>
<p><iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/LtOGa5M8AuU" frameborder="0" allowfullscreen=""></iframe></p>
<p>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/LtOGa5M8AuU"
frameborder="0" allowfullscreen=""></iframe>
</p>
<p>In a div</p>
<p><iframe width="560" height="315" src="https://www.youtube.com/embed/LtOGa5M8AuU" frameborder="0" allowfullscreen=""></iframe></p>
<h2>Foo</h2>
<p> Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </p>
<p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/LtOGa5M8AuU"
frameborder="0" allowfullscreen=""></iframe>
</p>
<h2>Foo</h2>
<p>Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</article>
</div>
</div>

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta charset="utf-8">
<title>Embedded videos test</title>
</head>
<body>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save