<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

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.
*/

'use strict';

const React = require('react');

const MatrixClientPeg = require('../../../MatrixClientPeg');

import {formatDate} from '../../../DateUtils';
const filesize = require('filesize');
const AccessibleButton = require('../../../components/views/elements/AccessibleButton');
const Modal = require('../../../Modal');
const sdk = require('../../../index');
import { _t } from '../../../languageHandler';

module.exports = React.createClass({
    displayName: 'ImageView',

    propTypes: {
        src: React.PropTypes.string.isRequired, // the source of the image being displayed
        name: React.PropTypes.string, // the main title ('name') for the image
        link: React.PropTypes.string, // the link (if any) applied to the name of the image
        width: React.PropTypes.number, // width of the image src in pixels
        height: React.PropTypes.number, // height of the image src in pixels
        fileSize: React.PropTypes.number, // size of the image src in bytes
        onFinished: React.PropTypes.func.isRequired, // callback when the lightbox is dismissed

        // the event (if any) that the Image is displaying. Used for event-specific stuff like
        // redactions, senders, timestamps etc.  Other descriptors are taken from the explicit
        // properties above, which let us use lightboxes to display images which aren't associated
        // with events.
        mxEvent: React.PropTypes.object,
    },

    // XXX: keyboard shortcuts for managing dialogs should be done by the modal
    // dialog base class somehow, surely...
    componentDidMount: function() {
        document.addEventListener("keydown", this.onKeyDown);
    },

    componentWillUnmount: function() {
        document.removeEventListener("keydown", this.onKeyDown);
    },

    onKeyDown: function(ev) {
        if (ev.keyCode == 27) { // escape
            ev.stopPropagation();
            ev.preventDefault();
            this.props.onFinished();
        }
    },

    onRedactClick: function() {
        const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
        Modal.createTrackedDialog('Confirm Redact Dialog', 'Image View', ConfirmRedactDialog, {
            onFinished: (proceed) => {
                if (!proceed) return;
                const self = this;
                MatrixClientPeg.get().redactEvent(
                    this.props.mxEvent.getRoomId(), this.props.mxEvent.getId(),
                ).catch(function(e) {
                    const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                    // display error message stating you couldn't delete this.
                    const code = e.errcode || e.statusCode;
                    Modal.createTrackedDialog('You cannot delete this image.', '', ErrorDialog, {
                        title: _t('Error'),
                        description: _t('You cannot delete this image. (%(code)s)', {code: code}),
                    });
                }).done();
            },
        });
    },

    getName: function() {
        let name = this.props.name;
        if (name && this.props.link) {
            name = { name };
        }
        return name;
    },

    render: function() {
/*
        // In theory max-width: 80%, max-height: 80% on the CSS should work
        // but in practice, it doesn't, so do it manually:

        var width = this.props.width || 500;
        var height = this.props.height || 500;

        var maxWidth = document.documentElement.clientWidth * 0.8;
        var maxHeight = document.documentElement.clientHeight * 0.8;

        var widthFrac = width / maxWidth;
        var heightFrac = height / maxHeight;

        var displayWidth;
        var displayHeight;
        if (widthFrac > heightFrac) {
            displayWidth = Math.min(width, maxWidth);
            displayHeight = (displayWidth / width) * height;
        } else {
            displayHeight = Math.min(height, maxHeight);
            displayWidth = (displayHeight / height) * width;
        }

        var style = {
            width: displayWidth,
            height: displayHeight
        };
*/
        let style; let res;

        if (this.props.width && this.props.height) {
            style = {
                width: this.props.width,
                height: this.props.height,
            };
            res = style.width + "x" + style.height + "px";
        }

        let size;
        if (this.props.fileSize) {
            size = filesize(this.props.fileSize);
        }

        let size_res;
        if (size && res) {
            size_res = size + ", " + res;
        } else {
            size_res = size || res;
        }

        const showEventMeta = !!this.props.mxEvent;

        let eventMeta;
        if (showEventMeta) {
            // Figure out the sender, defaulting to mxid
            let sender = this.props.mxEvent.getSender();
            const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
            if (room) {
                const member = room.getMember(sender);
                if (member) sender = member.name;
            }

            eventMeta = (

                { _t('Uploaded on %(date)s by %(user)s', {date: formatDate(new Date(this.props.mxEvent.getTs())), user: sender}) }

);
        }

        let eventRedact;
        if (showEventMeta) {
            eventRedact = (

                { _t('Remove') }

);
        }

        return (

                            <AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }></AccessibleButton>

                                { this.getName() }

                            { eventMeta }

                                        { _t('Download this file') }

                                         { size_res }

                            { eventRedact }

        );
    },
});
<commit_msg>Set title attribute on images in lightbox<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

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.
*/

'use strict';

const React = require('react');

const MatrixClientPeg = require('../../../MatrixClientPeg');

import {formatDate} from '../../../DateUtils';
const filesize = require('filesize');
const AccessibleButton = require('../../../components/views/elements/AccessibleButton');
const Modal = require('../../../Modal');
const sdk = require('../../../index');
import { _t } from '../../../languageHandler';

module.exports = React.createClass({
    displayName: 'ImageView',

    propTypes: {
        src: React.PropTypes.string.isRequired, // the source of the image being displayed
        name: React.PropTypes.string, // the main title ('name') for the image
        link: React.PropTypes.string, // the link (if any) applied to the name of the image
        width: React.PropTypes.number, // width of the image src in pixels
        height: React.PropTypes.number, // height of the image src in pixels
        fileSize: React.PropTypes.number, // size of the image src in bytes
        onFinished: React.PropTypes.func.isRequired, // callback when the lightbox is dismissed

        // the event (if any) that the Image is displaying. Used for event-specific stuff like
        // redactions, senders, timestamps etc.  Other descriptors are taken from the explicit
        // properties above, which let us use lightboxes to display images which aren't associated
        // with events.
        mxEvent: React.PropTypes.object,
    },

    // XXX: keyboard shortcuts for managing dialogs should be done by the modal
    // dialog base class somehow, surely...
    componentDidMount: function() {
        document.addEventListener("keydown", this.onKeyDown);
    },

    componentWillUnmount: function() {
        document.removeEventListener("keydown", this.onKeyDown);
    },

    onKeyDown: function(ev) {
        if (ev.keyCode == 27) { // escape
            ev.stopPropagation();
            ev.preventDefault();
            this.props.onFinished();
        }
    },

    onRedactClick: function() {
        const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
        Modal.createTrackedDialog('Confirm Redact Dialog', 'Image View', ConfirmRedactDialog, {
            onFinished: (proceed) => {
                if (!proceed) return;
                const self = this;
                MatrixClientPeg.get().redactEvent(
                    this.props.mxEvent.getRoomId(), this.props.mxEvent.getId(),
                ).catch(function(e) {
                    const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                    // display error message stating you couldn't delete this.
                    const code = e.errcode || e.statusCode;
                    Modal.createTrackedDialog('You cannot delete this image.', '', ErrorDialog, {
                        title: _t('Error'),
                        description: _t('You cannot delete this image. (%(code)s)', {code: code}),
                    });
                }).done();
            },
        });
    },

    getName: function() {
        let name = this.props.name;
        if (name && this.props.link) {
            name = { name };
        }
        return name;
    },

    render: function() {
/*
        // In theory max-width: 80%, max-height: 80% on the CSS should work
        // but in practice, it doesn't, so do it manually:

        var width = this.props.width || 500;
        var height = this.props.height || 500;

        var maxWidth = document.documentElement.clientWidth * 0.8;
        var maxHeight = document.documentElement.clientHeight * 0.8;

        var widthFrac = width / maxWidth;
        var heightFrac = height / maxHeight;

        var displayWidth;
        var displayHeight;
        if (widthFrac > heightFrac) {
            displayWidth = Math.min(width, maxWidth);
            displayHeight = (displayWidth / width) * height;
        } else {
            displayHeight = Math.min(height, maxHeight);
            displayWidth = (displayHeight / height) * width;
        }

        var style = {
            width: displayWidth,
            height: displayHeight
        };
*/
        let style; let res;

        if (this.props.width && this.props.height) {
            style = {
                width: this.props.width,
                height: this.props.height,
            };
            res = style.width + "x" + style.height + "px";
        }

        let size;
        if (this.props.fileSize) {
            size = filesize(this.props.fileSize);
        }

        let size_res;
        if (size && res) {
            size_res = size + ", " + res;
        } else {
            size_res = size || res;
        }

        const showEventMeta = !!this.props.mxEvent;

        let eventMeta;
        if (showEventMeta) {
            // Figure out the sender, defaulting to mxid
            let sender = this.props.mxEvent.getSender();
            const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
            if (room) {
                const member = room.getMember(sender);
                if (member) sender = member.name;
            }

            eventMeta = (

                { _t('Uploaded on %(date)s by %(user)s', {date: formatDate(new Date(this.props.mxEvent.getTs())), user: sender}) }

);
        }

        let eventRedact;
        if (showEventMeta) {
            eventRedact = (

                { _t('Remove') }

);
        }

        return (

                            <AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }></AccessibleButton>

                                { this.getName() }

                            { eventMeta }

                                        { _t('Download this file') }

                                         { size_res }

                            { eventRedact }

        );
    },
});
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd

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.
*/

'use strict';

import React from 'react';
import PropTypes from 'prop-types';
import filesize from 'filesize';
import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {decryptFile} from '../../../utils/DecryptFile';
import Tinter from '../../../Tinter';
import request from 'browser-request';
import Modal from '../../../Modal';

// A cached tinted copy of "img/download.svg"
let tintedDownloadImageURL;
// Track a list of mounted MFileBody instances so that we can update
// the "img/download.svg" when the tint changes.
let nextMountId = 0;
const mounts = {};

/**
 * Updates the tinted copy of "img/download.svg" when the tint changes.
 */
function updateTintedDownloadImage() {
    // Download the svg as an XML document.
    // We could cache the XML response here, but since the tint rarely changes
    // it's probably not worth it.
    // Also note that we can't use fetch here because fetch doesn't support
    // file URLs, which the download image will be if we're running from
    // the filesystem (like in an Electron wrapper).
    request({uri: "img/download.svg"}, (err, response, body) => {
        if (err) return;

        const svg = new DOMParser().parseFromString(body, "image/svg+xml");
        // Apply the fixups to the XML.
        const fixups = Tinter.calcSvgFixups([{contentDocument: svg}]);
        Tinter.applySvgFixups(fixups);
        // Encoded the fixed up SVG as a data URL.
        const svgString = new XMLSerializer().serializeToString(svg);
        tintedDownloadImageURL = "data:image/svg+xml;base64," + window.btoa(svgString);
        // Notify each mounted MFileBody that the URL has changed.
        Object.keys(mounts).forEach(function(id) {
            mounts[id].tint();
        });
    });
}

Tinter.registerTintable(updateTintedDownloadImage);

// User supplied content can contain scripts, we have to be careful that
// we don't accidentally run those script within the same origin as the
// client. Otherwise those scripts written by remote users can read
// the access token and end-to-end keys that are in local storage.
//
// For attachments downloaded directly from the homeserver we can use
// Content-Security-Policy headers to disable script execution.
//
// But attachments with end-to-end encryption are more difficult to handle.
// We need to decrypt the attachment on the client and then display it.
// To display the attachment we need to turn the decrypted bytes into a URL.
//
// There are two ways to turn bytes into URLs, data URL and blob URLs.
// Data URLs aren't suitable for downloading a file because Chrome has a
// 2MB limit on the size of URLs that can be viewed in the browser or
// downloaded. This limit does not seem to apply when the url is used as
// the source attribute of an image tag.
//
// Blob URLs are generated using window.URL.createObjectURL and unfortunately
// for our purposes they inherit the origin of the page that created them.
// This means that any scripts that run when the URL is viewed will be able
// to access local storage.
//
// The easiest solution is to host the code that generates the blob URL on
// a different domain to the client.
// Another possibility is to generate the blob URL within a sandboxed iframe.
// The downside of using a second domain is that it complicates hosting,
// the downside of using a sandboxed iframe is that the browers are overly
// restrictive in what you are allowed to do with the generated URL.
//
// For now given how unusable the blobs generated in sandboxed iframes are we
// default to using a renderer hosted on "usercontent.riot.im". This is
// overridable so that people running their own version of the client can
// choose a different renderer.
//
// To that end the current version of the blob generation is the following
// html:
//
//
//
// This waits to receive a message event sent using the window.postMessage API.
// When it receives the event it evals a javascript function in data.code and
// runs the function passing the event as an argument. This version adds
// support for a query parameter controlling the origin from which messages
// will be processed as an extra layer of security (note that the default URL
// is still 'v1' since it is backwards compatible).
//
// In particular it means that the rendering function can be written as a
// ordinary javascript function which then is turned into a string using
// toString().
//
const DEFAULT_CROSS_ORIGIN_RENDERER = "https://usercontent.riot.im/v1.html";

/**
 * Render the attachment inside the iframe.
 * We can't use imported libraries here so this has to be vanilla JS.
 */
function remoteRender(event) {
    const data = event.data;

    const img = document.createElement("img");
    img.id = "img";
    img.src = data.imgSrc;

    const a = document.createElement("a");
    a.id = "a";
    a.rel = data.rel;
    a.target = data.target;
    a.download = data.download;
    a.style = data.style;
    a.href = window.URL.createObjectURL(data.blob);
    a.appendChild(img);
    a.appendChild(document.createTextNode(data.textContent));

    const body = document.body;
    // Don't display scrollbars if the link takes more than one line
    // to display.
    body.style = "margin: 0px; overflow: hidden";
    body.appendChild(a);
}

/**
 * Update the tint inside the iframe.
 * We can't use imported libraries here so this has to be vanilla JS.
 */
function remoteSetTint(event) {
    const data = event.data;

    const img = document.getElementById("img");
    img.src = data.imgSrc;
    img.style = data.imgStyle;

    const a = document.getElementById("a");
    a.style = data.style;
}

/**
 * Get the current CSS style for a DOMElement.
 * @param {HTMLElement} element The element to get the current style of.
 * @return {string} The CSS style encoded as a string.
 */
function computedStyle(element) {
    if (!element) {
        return "";
    }
    const style = window.getComputedStyle(element, null);
    let cssText = style.cssText;
    if (cssText == "") {
        // Firefox doesn't implement ".cssText" for computed styles.
        // https://bugzilla.mozilla.org/show_bug.cgi?id=137687
        for (let i = 0; i < style.length; i++) {
            cssText += style[i] + ":";
            cssText += style.getPropertyValue(style[i]) + ";";
        }
    }
    return cssText;
}

module.exports = React.createClass({
    displayName: 'MFileBody',

    getInitialState: function() {
        return {
            decryptedBlob: (this.props.decryptedBlob ? this.props.decryptedBlob : null),
        };
    },

    contextTypes: {
        appConfig: PropTypes.object,
    },

    /**
     * Extracts a human readable label for the file attachment to use as
     * link text.
     *
     * @params {Object} content The "content" key of the matrix event.
     * @return {string} the human readable link text for the attachment.
     */
    presentableTextForFile: function(content) {
        let linkText = _t("Attachment");
        if (content.body && content.body.length > 0) {
            // The content body should be the name of the file including a
            // file extension.
            linkText = content.body;
        }

        if (content.info && content.info.size) {
            // If we know the size of the file then add it as human readable
            // string to the end of the link text so that the user knows how
            // big a file they are downloading.
            // The content.info also contains a MIME-type but we don't display
            // it since it is "ugly", users generally aren't aware what it
            // means and the type of the attachment can usually be inferrered
            // from the file extension.
            linkText += ' (' + filesize(content.info.size) + ')';
        }
        return linkText;
    },

    _getContentUrl: function() {
        const content = this.props.mxEvent.getContent();
        return MatrixClientPeg.get().mxcUrlToHttp(content.url);
    },

    componentDidMount: function() {
        // Add this to the list of mounted components to receive notifications
        // when the tint changes.
        this.id = nextMountId++;
        mounts[this.id] = this;
        this.tint();
    },

    componentWillUnmount: function() {
        // Remove this from the list of mounted components
        delete mounts[this.id];
    },

    tint: function() {
        // Update our tinted copy of "img/download.svg"
        if (this.refs.downloadImage) {
            this.refs.downloadImage.src = tintedDownloadImageURL;
        }
        if (this.refs.iframe) {
            // If the attachment is encrypted then the download image
            // will be inside the iframe so we wont be able to update
            // it directly.
            this.refs.iframe.contentWindow.postMessage({
                code: remoteSetTint.toString(),
                imgSrc: tintedDownloadImageURL,
                style: computedStyle(this.refs.dummyLink),
            }, "*");
        }
    },

    render: function() {
        const content = this.props.mxEvent.getContent();
        const text = this.presentableTextForFile(content);
        const isEncrypted = content.file !== undefined;
        const fileName = content.body && content.body.length > 0 ? content.body : _t("Attachment");
        const contentUrl = this._getContentUrl();
        const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");

        if (isEncrypted) {
            if (this.state.decryptedBlob === null) {
                // Need to decrypt the attachment
                // Wait for the user to click on the link before downloading
                // and decrypting the attachment.
                let decrypting = false;
                const decrypt = () => {
                    if (decrypting) {
                        return false;
                    }
                    decrypting = true;
                    decryptFile(content.file).then((blob) => {
                        this.setState({
                            decryptedBlob: blob,
                        });
                    }).catch((err) => {
                        console.warn("Unable to decrypt attachment: ", err);
                        Modal.createTrackedDialog('Error decrypting attachment', '', ErrorDialog, {
                            title: _t("Error"),
                            description: _t("Error decrypting attachment"),
                        });
                    }).finally(() => {
                        decrypting = false;
                        return;
                    });
                };

                return (

                                { _t("Decrypt %(text)s", { text: text }) }

                );
            }

            // When the iframe loads we tell it to render a download link
            const onIframeLoad = (ev) => {
                ev.target.contentWindow.postMessage({
                    code: remoteRender.toString(),
                    imgSrc: tintedDownloadImageURL,
                    style: computedStyle(this.refs.dummyLink),
                    blob: this.state.decryptedBlob,
                    // Set a download attribute for encrypted files so that the file
                    // will have the correct name when the user tries to download it.
                    // We can't provide a Content-Disposition header like we would for HTTP.
                    download: fileName,
                    target: "_blank",
                    textContent: _t("Download %(text)s", { text: text }),
                }, "*");
            };

            // If the attachment is encryped then put the link inside an iframe.
            let renderer_url = DEFAULT_CROSS_ORIGIN_RENDERER;
            if (this.context.appConfig && this.context.appConfig.cross_origin_renderer_url) {
                renderer_url = this.context.appConfig.cross_origin_renderer_url;
            }
            renderer_url += "?origin=" + encodeURIComponent(window.location.origin);
            return (

                            { /*
                              * Add dummy copy of the "a" tag
                              * We'll use it to learn how the download link
                              * would have been styled if it was rendered inline.
                              */ }

            );
        } else if (contentUrl) {
            // If the attachment is not encrypted then we check whether we
            // are being displayed in the room timeline or in a list of
            // files in the right hand side of the screen.
            if (this.props.tileShape === "file_grid") {
                return (

                                { fileName }

                                { content.info && content.info.size ? filesize(content.info.size) : "" }

                );
            } else {
                return (

                                { _t("Download %(text)s", { text: text }) }

                );
            }
        } else {
            const extra = text ? (': ' + text) : '';
            return
                { _t("Invalid file%(extra)s", { extra: extra }) }
            ;
        }
    },
});
<commit_msg>add rel noopener to cross origin loader (currently rel="undefined")<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd

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.
*/

'use strict';

import React from 'react';
import PropTypes from 'prop-types';
import filesize from 'filesize';
import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {decryptFile} from '../../../utils/DecryptFile';
import Tinter from '../../../Tinter';
import request from 'browser-request';
import Modal from '../../../Modal';

// A cached tinted copy of "img/download.svg"
let tintedDownloadImageURL;
// Track a list of mounted MFileBody instances so that we can update
// the "img/download.svg" when the tint changes.
let nextMountId = 0;
const mounts = {};

/**
 * Updates the tinted copy of "img/download.svg" when the tint changes.
 */
function updateTintedDownloadImage() {
    // Download the svg as an XML document.
    // We could cache the XML response here, but since the tint rarely changes
    // it's probably not worth it.
    // Also note that we can't use fetch here because fetch doesn't support
    // file URLs, which the download image will be if we're running from
    // the filesystem (like in an Electron wrapper).
    request({uri: "img/download.svg"}, (err, response, body) => {
        if (err) return;

        const svg = new DOMParser().parseFromString(body, "image/svg+xml");
        // Apply the fixups to the XML.
        const fixups = Tinter.calcSvgFixups([{contentDocument: svg}]);
        Tinter.applySvgFixups(fixups);
        // Encoded the fixed up SVG as a data URL.
        const svgString = new XMLSerializer().serializeToString(svg);
        tintedDownloadImageURL = "data:image/svg+xml;base64," + window.btoa(svgString);
        // Notify each mounted MFileBody that the URL has changed.
        Object.keys(mounts).forEach(function(id) {
            mounts[id].tint();
        });
    });
}

Tinter.registerTintable(updateTintedDownloadImage);

// User supplied content can contain scripts, we have to be careful that
// we don't accidentally run those script within the same origin as the
// client. Otherwise those scripts written by remote users can read
// the access token and end-to-end keys that are in local storage.
//
// For attachments downloaded directly from the homeserver we can use
// Content-Security-Policy headers to disable script execution.
//
// But attachments with end-to-end encryption are more difficult to handle.
// We need to decrypt the attachment on the client and then display it.
// To display the attachment we need to turn the decrypted bytes into a URL.
//
// There are two ways to turn bytes into URLs, data URL and blob URLs.
// Data URLs aren't suitable for downloading a file because Chrome has a
// 2MB limit on the size of URLs that can be viewed in the browser or
// downloaded. This limit does not seem to apply when the url is used as
// the source attribute of an image tag.
//
// Blob URLs are generated using window.URL.createObjectURL and unfortunately
// for our purposes they inherit the origin of the page that created them.
// This means that any scripts that run when the URL is viewed will be able
// to access local storage.
//
// The easiest solution is to host the code that generates the blob URL on
// a different domain to the client.
// Another possibility is to generate the blob URL within a sandboxed iframe.
// The downside of using a second domain is that it complicates hosting,
// the downside of using a sandboxed iframe is that the browers are overly
// restrictive in what you are allowed to do with the generated URL.
//
// For now given how unusable the blobs generated in sandboxed iframes are we
// default to using a renderer hosted on "usercontent.riot.im". This is
// overridable so that people running their own version of the client can
// choose a different renderer.
//
// To that end the current version of the blob generation is the following
// html:
//
//
//
// This waits to receive a message event sent using the window.postMessage API.
// When it receives the event it evals a javascript function in data.code and
// runs the function passing the event as an argument. This version adds
// support for a query parameter controlling the origin from which messages
// will be processed as an extra layer of security (note that the default URL
// is still 'v1' since it is backwards compatible).
//
// In particular it means that the rendering function can be written as a
// ordinary javascript function which then is turned into a string using
// toString().
//
const DEFAULT_CROSS_ORIGIN_RENDERER = "https://usercontent.riot.im/v1.html";

/**
 * Render the attachment inside the iframe.
 * We can't use imported libraries here so this has to be vanilla JS.
 */
function remoteRender(event) {
    const data = event.data;

    const img = document.createElement("img");
    img.id = "img";
    img.src = data.imgSrc;

    const a = document.createElement("a");
    a.id = "a";
    a.rel = data.rel;
    a.target = data.target;
    a.download = data.download;
    a.style = data.style;
    a.href = window.URL.createObjectURL(data.blob);
    a.appendChild(img);
    a.appendChild(document.createTextNode(data.textContent));

    const body = document.body;
    // Don't display scrollbars if the link takes more than one line
    // to display.
    body.style = "margin: 0px; overflow: hidden";
    body.appendChild(a);
}

/**
 * Update the tint inside the iframe.
 * We can't use imported libraries here so this has to be vanilla JS.
 */
function remoteSetTint(event) {
    const data = event.data;

    const img = document.getElementById("img");
    img.src = data.imgSrc;
    img.style = data.imgStyle;

    const a = document.getElementById("a");
    a.style = data.style;
}

/**
 * Get the current CSS style for a DOMElement.
 * @param {HTMLElement} element The element to get the current style of.
 * @return {string} The CSS style encoded as a string.
 */
function computedStyle(element) {
    if (!element) {
        return "";
    }
    const style = window.getComputedStyle(element, null);
    let cssText = style.cssText;
    if (cssText == "") {
        // Firefox doesn't implement ".cssText" for computed styles.
        // https://bugzilla.mozilla.org/show_bug.cgi?id=137687
        for (let i = 0; i < style.length; i++) {
            cssText += style[i] + ":";
            cssText += style.getPropertyValue(style[i]) + ";";
        }
    }
    return cssText;
}

module.exports = React.createClass({
    displayName: 'MFileBody',

    getInitialState: function() {
        return {
            decryptedBlob: (this.props.decryptedBlob ? this.props.decryptedBlob : null),
        };
    },

    contextTypes: {
        appConfig: PropTypes.object,
    },

    /**
     * Extracts a human readable label for the file attachment to use as
     * link text.
     *
     * @params {Object} content The "content" key of the matrix event.
     * @return {string} the human readable link text for the attachment.
     */
    presentableTextForFile: function(content) {
        let linkText = _t("Attachment");
        if (content.body && content.body.length > 0) {
            // The content body should be the name of the file including a
            // file extension.
            linkText = content.body;
        }

        if (content.info && content.info.size) {
            // If we know the size of the file then add it as human readable
            // string to the end of the link text so that the user knows how
            // big a file they are downloading.
            // The content.info also contains a MIME-type but we don't display
            // it since it is "ugly", users generally aren't aware what it
            // means and the type of the attachment can usually be inferrered
            // from the file extension.
            linkText += ' (' + filesize(content.info.size) + ')';
        }
        return linkText;
    },

    _getContentUrl: function() {
        const content = this.props.mxEvent.getContent();
        return MatrixClientPeg.get().mxcUrlToHttp(content.url);
    },

    componentDidMount: function() {
        // Add this to the list of mounted components to receive notifications
        // when the tint changes.
        this.id = nextMountId++;
        mounts[this.id] = this;
        this.tint();
    },

    componentWillUnmount: function() {
        // Remove this from the list of mounted components
        delete mounts[this.id];
    },

    tint: function() {
        // Update our tinted copy of "img/download.svg"
        if (this.refs.downloadImage) {
            this.refs.downloadImage.src = tintedDownloadImageURL;
        }
        if (this.refs.iframe) {
            // If the attachment is encrypted then the download image
            // will be inside the iframe so we wont be able to update
            // it directly.
            this.refs.iframe.contentWindow.postMessage({
                code: remoteSetTint.toString(),
                imgSrc: tintedDownloadImageURL,
                style: computedStyle(this.refs.dummyLink),
            }, "*");
        }
    },

    render: function() {
        const content = this.props.mxEvent.getContent();
        const text = this.presentableTextForFile(content);
        const isEncrypted = content.file !== undefined;
        const fileName = content.body && content.body.length > 0 ? content.body : _t("Attachment");
        const contentUrl = this._getContentUrl();
        const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");

        if (isEncrypted) {
            if (this.state.decryptedBlob === null) {
                // Need to decrypt the attachment
                // Wait for the user to click on the link before downloading
                // and decrypting the attachment.
                let decrypting = false;
                const decrypt = () => {
                    if (decrypting) {
                        return false;
                    }
                    decrypting = true;
                    decryptFile(content.file).then((blob) => {
                        this.setState({
                            decryptedBlob: blob,
                        });
                    }).catch((err) => {
                        console.warn("Unable to decrypt attachment: ", err);
                        Modal.createTrackedDialog('Error decrypting attachment', '', ErrorDialog, {
                            title: _t("Error"),
                            description: _t("Error decrypting attachment"),
                        });
                    }).finally(() => {
                        decrypting = false;
                        return;
                    });
                };

                return (

                                { _t("Decrypt %(text)s", { text: text }) }

                );
            }

            // When the iframe loads we tell it to render a download link
            const onIframeLoad = (ev) => {
                ev.target.contentWindow.postMessage({
                    code: remoteRender.toString(),
                    imgSrc: tintedDownloadImageURL,
                    style: computedStyle(this.refs.dummyLink),
                    blob: this.state.decryptedBlob,
                    // Set a download attribute for encrypted files so that the file
                    // will have the correct name when the user tries to download it.
                    // We can't provide a Content-Disposition header like we would for HTTP.
                    download: fileName,
                    rel: "noopener",
                    target: "_blank",
                    textContent: _t("Download %(text)s", { text: text }),
                }, "*");
            };

            // If the attachment is encryped then put the link inside an iframe.
            let renderer_url = DEFAULT_CROSS_ORIGIN_RENDERER;
            if (this.context.appConfig && this.context.appConfig.cross_origin_renderer_url) {
                renderer_url = this.context.appConfig.cross_origin_renderer_url;
            }
            renderer_url += "?origin=" + encodeURIComponent(window.location.origin);
            return (

                            { /*
                              * Add dummy copy of the "a" tag
                              * We'll use it to learn how the download link
                              * would have been styled if it was rendered inline.
                              */ }

            );
        } else if (contentUrl) {
            // If the attachment is not encrypted then we check whether we
            // are being displayed in the room timeline or in a list of
            // files in the right hand side of the screen.
            if (this.props.tileShape === "file_grid") {
                return (

                                { fileName }

                                { content.info && content.info.size ? filesize(content.info.size) : "" }

                );
            } else {
                return (

                                { _t("Download %(text)s", { text: text }) }

                );
            }
        } else {
            const extra = text ? (': ' + text) : '';
            return
                { _t("Invalid file%(extra)s", { extra: extra }) }
            ;
        }
    },
});
<|endoftext|>


<commit_before>var PathModule = require('path'),
    events = require('events'),
    constants = require('constants');

var CONC = 5;
function setMaxStatsAtOnce(n) { CONC = n; }

// Wildcard directory listing. There is no way that a client should
// use multiple wildcards in an FTP command, or wildcards which
// are part of a directory name rather than a filename (unless
// they're identifying a unique directory to be listed).
// So this can be pretty simple.

function statList(fsm, list, callback) {
    if (list.length == 0)
        return callback(null, []);

    var stats = [];
    var i = 0, j = 0;
    for (i = 0; i < list.length && i < CONC; ++i)
        handleFile(i);
    j = --i;

    var erroredOut = false;
    function handleFile(ii) {
        if (erroredOut)
            return;
        if (i >= list.length)
            return i == list.length + j ? finished() : null;

        fsm.stat(list[i], function (err, st) {
            if (err) {
                erroredOut = true;
                callback(err);
            }
            else {
                stats.push({ name: PathModule.basename(list[ii]),
                             stats: st });
                handleFile(++i);
            }
        });
    }

    function finished() {
        callback(null, stats);
    }
}

function matchPattern(pattern, string) {
    var pi = 0, si = 0;
    for (; si < string.length && pi < pattern.length; ++si) {
        var c = string.charAt(si);
        var pc = pattern.charAt(pi);

        if (pc == '*') {
            if (pi + 1 == pattern.length) {
                ;
            }
            else if (pattern.charAt(pi+1) == '*') {
                --si;
                ++pi;
            }
            else if (pattern.charAt(pi+1) == '?') {
                ++pi;
            }
            else if (si < string.length-1 && pattern.charAt(pi+1) == string.charAt(si)) {
                pi += 2;
            }
        }
        else if (pc == '?') {
            ++pi;
        }
        else if (pc == c) {
            ++pi;
        }
        else {
            return false;
        }
    }

    return (pi == pattern.length || (pi == pattern.length - 1 && pattern.charAt(pi) == '*')) &&
           si == string.length;
}
/*console.log(matchPattern("foo*", "foooxx") == true);
console.log(matchPattern("foo*", "foo") == true);
console.log(matchPattern("foo*", "fo") == false);
console.log(matchPattern("foo*", "") == false);
console.log(matchPattern("*foo*", "foo") == true);
console.log(matchPattern("*foo*", "foio") == false);
console.log(matchPattern("*.txt", ".txt") == true);
console.log(matchPattern("*.txt", "xxx.txt") == true);
console.log(matchPattern("*.txt", "xxx.txty") == false);
console.log(matchPattern("*.txt?", "xxx.txty") == true);
console.log(matchPattern("?a?b?c", "1a1b1c") == true);
console.log(matchPattern("?a?b?c*.foo", "1a1b1cblah.foo") == true);
console.log(matchPattern("?a?b?c*.foo", "1a1b1cblah.foob") == false);
console.log(matchPattern("???", "xyz") == true);
console.log(matchPattern("???", "xyzq") == false);
console.log(matchPattern("?", "x") == true);
console.log(matchPattern("?", "xx") == false);
console.log(matchPattern("***", "xx") == true);
console.log(matchPattern("***.foo", "xx.foo") == true);
console.log(matchPattern("***.foo", "xx.foob") == false);
console.log(matchPattern("*", "") == true);
console.log(matchPattern("?", "") == false);*/

function glob(path, fsm, callback, noWildcards) {
    if (! callback) {
        callback = fsm;
        fsm = require('fs');
    }

    var s = path.indexOf('*');
    var q = path.indexOf('?');
    var w;
    for (w = 0; !noWildcards && w < path.length && path.charAt(w) != '*' && path.charAt(w) != '?'; ++w);

    if (w == path.length) { // There are no wildcards.
        fsm.readdir(path, function (err, contents) {
            if (err) {
                if (err.errno == constants.ENOTDIR) {
                    statList(fsm, [path], function (err, list) {
                        if (err)
                            return callback(err);
                        if (list.length != 1)
                            throw new Error("Internal error in glob.js");
                        callback(null, list);
                    });
                }
                else if (err.errno == constants.ENOENT) {
                    callback(null, []);
                }
                else {
                    callback(err);
                }
            }
            else {
                statList(fsm, contents.map(function (p) { return PathModule.join(path, p); }), function (err, list) {
                    if (err)
                        callback(err);
                    else
                        callback(null, list);
                });
            }
        });

        return;
    }
    else {
        // Check that there is no '/' after the first wildcard.

        var i;
        for (i = w; i < path.length; ++i) {
            if (path.charAt(i) == '/')
                return callback(null, []);
        }

        var base = "", pattern;
        for (i = w; i >= 0; --i) {
            if (path.charAt(i) == '/') {
                base = path.substr(0, i+1)
                break;
            }
        }
        pattern = path.substr(i == 0 ? 0 : i + 1);

        // Remove any leading/trailing slashes which might still
        // be present if the path containts multiple slashes.
        for (i = 0; i < pattern.length && pattern.charAt(i) == '/'; ++i);
        if (i > 0)
            pattern = pattern.substr(i);
        for (i = base.length-1; i > 0 && base.charAt(i) == '/'; --i);
        if (i != base.length-1)
            base = base.substr(0, i+1);

        // We now have the base path in 'base' (possibly the empty string)
        // and the wildcard filename pattern in 'pattern'.

        readTheDir(false);
        function readTheDir(listingSingleDir) {
            fsm.readdir(base, function (err, contents) {
                if (err) {
                    if (err.errno == constants.ENOTDIR || err.errno == constants.ENOENT) {
                        callback(null, []);
                    }
                    else {
                        callback(err);
                    }
                }
                else {
                    var matches;
                    if (! listingSingleDir)
                        matches = contents.filter(function (n) { return matchPattern(pattern, n); });
                    else
                        matches = contents;

                    // Special case. If we have exactly one match, and it's a directory, then list
                    // the contents of that directory. (There's no reason why anyone should want
                    // to identify mutliple directories using wildcards and then list all of their
                    // contents over FTP!)
                    if (!listingSingleDir && matches.length == 1) {
                        var dir = PathModule.join(base, matches[0]);
                        fsm.stat(dir, function (err, st) {
                            if (err)
                                return callback(err);

                            if (! st.isDirectory()) {
                                doTheNormalThing();
                            }
                            else {
                                base = dir;
                                readTheDir(/*listingSingleDir=*/true);
                            }
                        });
                    }
                    else {
                        doTheNormalThing();
                    }

                    function doTheNormalThing() {
                        statList(fsm, matches.map(function (p) { return PathModule.join(base, p); }), function (err, list) {
                            if (err)
                                callback(err);
                            else
                                callback(null, list);
                        });
                    }
                }
            });
        }
    }
}

exports.glob = glob;
exports.matchPattern = matchPattern;
exports.setMaxStatsAtOnce = setMaxStatsAtOnce;
<commit_msg>Minor (fix typo in glob.js)<commit_after>var PathModule = require('path'),
    events = require('events'),
    constants = require('constants');

var CONC = 5;
function setMaxStatsAtOnce(n) { CONC = n; }

// Wildcard directory listing. There is no way that a client should
// use multiple wildcards in an FTP command, or wildcards which
// are part of a directory name rather than a filename (unless
// they're identifying a unique directory to be listed).
// So this can be pretty simple.

function statList(fsm, list, callback) {
    if (list.length == 0)
        return callback(null, []);

    var stats = [];
    var i = 0, j = 0;
    for (i = 0; i < list.length && i < CONC; ++i)
        handleFile(i);
    j = --i;

    var erroredOut = false;
    function handleFile(ii) {
        if (erroredOut)
            return;
        if (i >= list.length)
            return i == list.length + j ? finished() : null;

        fsm.stat(list[i], function (err, st) {
            if (err) {
                erroredOut = true;
                callback(err);
            }
            else {
                stats.push({ name: PathModule.basename(list[ii]),
                             stats: st });
                handleFile(++i);
            }
        });
    }

    function finished() {
        callback(null, stats);
    }
}

function matchPattern(pattern, string) {
    var pi = 0, si = 0;
    for (; si < string.length && pi < pattern.length; ++si) {
        var c = string.charAt(si);
        var pc = pattern.charAt(pi);

        if (pc == '*') {
            if (pi + 1 == pattern.length) {
                ;
            }
            else if (pattern.charAt(pi+1) == '*') {
                --si;
                ++pi;
            }
            else if (pattern.charAt(pi+1) == '?') {
                ++pi;
            }
            else if (si < string.length-1 && pattern.charAt(pi+1) == string.charAt(si)) {
                pi += 2;
            }
        }
        else if (pc == '?') {
            ++pi;
        }
        else if (pc == c) {
            ++pi;
        }
        else {
            return false;
        }
    }

    return (pi == pattern.length || (pi == pattern.length - 1 && pattern.charAt(pi) == '*')) &&
           si == string.length;
}
/*console.log(matchPattern("foo*", "foooxx") == true);
console.log(matchPattern("foo*", "foo") == true);
console.log(matchPattern("foo*", "fo") == false);
console.log(matchPattern("foo*", "") == false);
console.log(matchPattern("*foo*", "foo") == true);
console.log(matchPattern("*foo*", "foio") == false);
console.log(matchPattern("*.txt", ".txt") == true);
console.log(matchPattern("*.txt", "xxx.txt") == true);
console.log(matchPattern("*.txt", "xxx.txty") == false);
console.log(matchPattern("*.txt?", "xxx.txty") == true);
console.log(matchPattern("?a?b?c", "1a1b1c") == true);
console.log(matchPattern("?a?b?c*.foo", "1a1b1cblah.foo") == true);
console.log(matchPattern("?a?b?c*.foo", "1a1b1cblah.foob") == false);
console.log(matchPattern("???", "xyz") == true);
console.log(matchPattern("???", "xyzq") == false);
console.log(matchPattern("?", "x") == true);
console.log(matchPattern("?", "xx") == false);
console.log(matchPattern("***", "xx") == true);
console.log(matchPattern("***.foo", "xx.foo") == true);
console.log(matchPattern("***.foo", "xx.foob") == false);
console.log(matchPattern("*", "") == true);
console.log(matchPattern("?", "") == false);*/

function glob(path, fsm, callback, noWildcards) {
    if (! callback) {
        callback = fsm;
        fsm = require('fs');
    }

    var s = path.indexOf('*');
    var q = path.indexOf('?');
    var w;
    for (w = 0; !noWildcards && w < path.length && path.charAt(w) != '*' && path.charAt(w) != '?'; ++w);

    if (w == path.length) { // There are no wildcards.
        fsm.readdir(path, function (err, contents) {
            if (err) {
                if (err.errno == constants.ENOTDIR) {
                    statList(fsm, [path], function (err, list) {
                        if (err)
                            return callback(err);
                        if (list.length != 1)
                            throw new Error("Internal error in glob.js");
                        callback(null, list);
                    });
                }
                else if (err.errno == constants.ENOENT) {
                    callback(null, []);
                }
                else {
                    callback(err);
                }
            }
            else {
                statList(fsm, contents.map(function (p) { return PathModule.join(path, p); }), function (err, list) {
                    if (err)
                        callback(err);
                    else
                        callback(null, list);
                });
            }
        });

        return;
    }
    else {
        // Check that there is no '/' after the first wildcard.

        var i;
        for (i = w; i < path.length; ++i) {
            if (path.charAt(i) == '/')
                return callback(null, []);
        }

        var base = "", pattern;
        for (i = w; i >= 0; --i) {
            if (path.charAt(i) == '/') {
                base = path.substr(0, i+1)
                break;
            }
        }
        pattern = path.substr(i == 0 ? 0 : i + 1);

        // Remove any leading/trailing slashes which might still
        // be present if the path contains multiple slashes.
        for (i = 0; i < pattern.length && pattern.charAt(i) == '/'; ++i);
        if (i > 0)
            pattern = pattern.substr(i);
        for (i = base.length-1; i > 0 && base.charAt(i) == '/'; --i);
        if (i != base.length-1)
            base = base.substr(0, i+1);

        // We now have the base path in 'base' (possibly the empty string)
        // and the wildcard filename pattern in 'pattern'.

        readTheDir(false);
        function readTheDir(listingSingleDir) {
            fsm.readdir(base, function (err, contents) {
                if (err) {
                    if (err.errno == constants.ENOTDIR || err.errno == constants.ENOENT) {
                        callback(null, []);
                    }
                    else {
                        callback(err);
                    }
                }
                else {
                    var matches;
                    if (! listingSingleDir)
                        matches = contents.filter(function (n) { return matchPattern(pattern, n); });
                    else
                        matches = contents;

                    // Special case. If we have exactly one match, and it's a directory, then list
                    // the contents of that directory. (There's no reason why anyone should want
                    // to identify mutliple directories using wildcards and then list all of their
                    // contents over FTP!)
                    if (!listingSingleDir && matches.length == 1) {
                        var dir = PathModule.join(base, matches[0]);
                        fsm.stat(dir, function (err, st) {
                            if (err)
                                return callback(err);

                            if (! st.isDirectory()) {
                                doTheNormalThing();
                            }
                            else {
                                base = dir;
                                readTheDir(/*listingSingleDir=*/true);
                            }
                        });
                    }
                    else {
                        doTheNormalThing();
                    }

                    function doTheNormalThing() {
                        statList(fsm, matches.map(function (p) { return PathModule.join(base, p); }), function (err, list) {
                            if (err)
                                callback(err);
                            else
                                callback(null, list);
                        });
                    }
                }
            });
        }
    }
}

exports.glob = glob;
exports.matchPattern = matchPattern;
exports.setMaxStatsAtOnce = setMaxStatsAtOnce;
<|endoftext|>


<commit_before>/**
 * Compression
 */

var zlib = require('zlib')
  , deflate = zlib.createDeflate
  , gzip = zlib.createGzip;

module.exports = function(options) {
  // options = options || {
  //   level: 9,
  //   windowBits: 15,
  //   memLevel: 9,
  //   strategy: 4
  // };
  return function(req, res, next) {
    res.setHeader('Vary', 'Accept-Encoding');

    var accept = req.headers['accept-encoding']
      , compress
      , algo
      , zip;

    if (!accept) return next();

    if (accept.trim() === '*'
        || ~accept.indexOf('gzip')) {
      algo = 'gzip';
      compress = gzip;
    } else if (~accept.indexOf('deflate')) {
      algo = 'deflate';
      compress = deflate;
    } else {
      return next();
    }

    var writeHead = res.writeHead;
    res.writeHead = function() {
      res.writeHead = writeHead;

      var head = arguments[2] || arguments[1] || 0;

      var type = head['Content-Type']
        || res.getHeader('Content-Type')
        || '';

      if (~type.indexOf('charset')) {
        res.setHeader('Content-Encoding', algo);
        res.removeHeader('Content-Length');

        zip = compress(options);

        zip.on('data', function(data) {
          write.call(res, data);
        });

        zip.on('end', function() {
          end.call(res);
        });

        zip.on('error', function(err) {
          if (!res.finished) end.call(res);
          console.error(err.stack || err + '');
        });
      } else {
        res.write = write;
        res.end = end;
      }

      return writeHead.apply(res, arguments);
    };

    var write = res.write;
    res.write = function(data, enc) {
      if (!res._header) {
        res.writeHead(res.statusCode || 200);
      }

      if (!zip) {
        return write.apply(res, arguments);
      }

      if (data) {
        if (enc) data = new Buffer(data, enc);
        return zip.write(data);
      }
    };

    var end = res.end;
    res.end = function(data, enc) {
      if (!res._header) {
        res.writeHead(res.statusCode || 200);
      }

      if (!zip) {
        return end.apply(res, arguments);
      }

      if (data) {
        if (enc) data = new Buffer(data, enc);
        zip.write(data);
      }

      return zip.end();
    };

    next();
  };
};
<commit_msg>ensure removed content-length in gzip middleware<commit_after>/**
 * Compression
 */

var zlib = require('zlib')
  , deflate = zlib.createDeflate
  , gzip = zlib.createGzip;

module.exports = function(options) {
  // options = options || {
  //   level: 9,
  //   windowBits: 15,
  //   memLevel: 9,
  //   strategy: 4
  // };
  return function(req, res, next) {
    res.setHeader('Vary', 'Accept-Encoding');

    var accept = req.headers['accept-encoding']
      , compress
      , algo
      , zip;

    if (!accept) return next();

    if (accept.trim() === '*'
        || ~accept.indexOf('gzip')) {
      algo = 'gzip';
      compress = gzip;
    } else if (~accept.indexOf('deflate')) {
      algo = 'deflate';
      compress = deflate;
    } else {
      return next();
    }

    var writeHead = res.writeHead;
    res.writeHead = function() {
      res.writeHead = writeHead;

      var head = arguments[2] || arguments[1] || 0;

      var type = head['Content-Type']
        || res.getHeader('Content-Type')
        || '';

      if (~type.indexOf('charset')) {
        if (head) {
          delete head['Content-Length'];
          delete head['Content-Encoding'];
        }

        res.setHeader('Content-Encoding', algo);
        res.removeHeader('Content-Length');

        zip = compress(options);

        zip.on('data', function(data) {
          write.call(res, data);
        });

        zip.on('end', function() {
          end.call(res);
        });

        zip.on('error', function(err) {
          if (!res.finished) end.call(res);
          console.error(err.stack || err + '');
        });
      } else {
        res.write = write;
        res.end = end;
      }

      return writeHead.apply(res, arguments);
    };

    var write = res.write;
    res.write = function(data, enc) {
      if (!res._header) {
        res.writeHead(res.statusCode || 200);
      }

      if (!zip) {
        return write.apply(res, arguments);
      }

      if (data) {
        if (enc) data = new Buffer(data, enc);
        return zip.write(data);
      }
    };

    var end = res.end;
    res.end = function(data, enc) {
      if (!res._header) {
        res.writeHead(res.statusCode || 200);
      }

      if (!zip) {
        return end.apply(res, arguments);
      }

      if (data) {
        if (enc) data = new Buffer(data, enc);
        zip.write(data);
      }

      return zip.end();
    };

    next();
  };
};
<|endoftext|>


<commit_before>var Haml;

(function () {

  var matchers, self_close_tags, embedder, forceXML;

  function html_escape(text) {
    return (text + "").
      replace(/&/g, "&").
      replace(/</g, "<").
      replace(/>/g, ">").
      replace(/\"/g, """);
  }

  function render_attribs(attribs) {
    var key, value, result = [];
    for (key in attribs) {
      if (key !== '_content' && attribs.hasOwnProperty(key)) {
        switch (attribs[key]) {
        case 'undefined':
        case 'false':
        case 'null':
        case '""':
          break;
        default:
          try {
            value = JSON.parse("[" + attribs[key] +"]")[0];
            if (value === true) {
              value = key;
            } else if (typeof value === 'string' && embedder.test(value)) {
              value = '" +\n' + parse_interpol(html_escape(value)) + ' +\n"';
            } else {
              value = html_escape(value);
            }
            result.push(" " + key + '=\\"' + value + '\\"');
          } catch (e) {
            result.push(" " + key + '=\\"" + html_escape(' + attribs[key] + ') + "\\"');
          }
        }
      }
    }
    return result.join("");
  }

  // Parse the attribute block using a state machine
  function parse_attribs(line) {
    var attributes = {},
        l = line.length,
        i, c,
        count = 1,
        quote = false,
        skip = false,
        open, close, joiner, seperator,
        pair = {
          start: 1,
          middle: null,
          end: null
        };

    if (!(l > 0 && (line.charAt(0) === '{' || line.charAt(0) === '('))) {
      return {
        _content: line[0] === ' ' ? line.substr(1, l) : line
      };
    }
    open = line.charAt(0);
    close = (open === '{') ? '}' : ')';
    joiner = (open === '{') ? ':' : '=';
    seperator = (open === '{') ? ',' : ' ';

    function process_pair() {
      if (typeof pair.start === 'number' &&
          typeof pair.middle === 'number' &&
          typeof pair.end === 'number') {
        var key = line.substr(pair.start, pair.middle - pair.start).trim(),
            value = line.substr(pair.middle + 1, pair.end - pair.middle - 1).trim();
        attributes[key] = value;
      }
      pair = {
        start: null,
        middle: null,
        end: null
      };
    }

    for (i = 1; count > 0; i += 1) {

      // If we reach the end of the line, then there is a problem
      if (i > l) {
        throw "Malformed attribute block";
      }

      c = line.charAt(i);
      if (skip) {
        skip = false;
      } else {
        if (quote) {
          if (c === '\\') {
            skip = true;
          }
          if (c === quote) {
            quote = false;
          }
        } else {
          if (c === '"' || c === "'") {
            quote = c;
          }

          if (count === 1) {
            if (c === joiner) {
              pair.middle = i;
            }
            if (c === seperator || c === close) {
              pair.end = i;
              process_pair();
              if (c === seperator) {
                pair.start = i + 1;
              }
            }
          }

          if (c === open || c === "(") {
            count += 1;
          }
          if (c === close || (count > 1 && c === ")")) {
            count -= 1;
          }
        }
      }
    }
    attributes._content = line.substr(i, line.length);
    return attributes;
  }

  // Split interpolated strings into an array of literals and code fragments.
  function parse_interpol(value) {
    var items = [],
        pos = 0,
        next = 0,
        match;
    while (true) {
      // Match up to embedded string
      next = value.substr(pos).search(embedder);
      if (next < 0) {
        if (pos < value.length) {
          items.push(JSON.stringify(value.substr(pos)));
        }
        break;
      }
      items.push(JSON.stringify(value.substr(pos, next)));
      pos += next;

      // Match embedded string
      match = value.substr(pos).match(embedder);
      next = match[0].length;
      if (next < 0) { break; }
      items.push(match[1] || match[2]);
      pos += next;
    }
    return items.filter(function (part) { return part && part.length > 0}).join(" +\n");
  }

  // Used to find embedded code in interpolated strings.
  embedder = /\#\{([^}]*)\}/;

  self_close_tags = ["meta", "img", "link", "br", "hr", "input", "area", "base"];

  // All matchers' regexps should capture leading whitespace in first capture
  // and trailing content in last capture
  matchers = [
    // html tags
    {
      regexp: /^(\s*)((?:[.#%][a-z_\-][a-z0-9_:\-]*)+)(.*)$/i,
      process: function () {
        var tag, classes, ids, attribs, content;
        tag = this.matches[2];
        classes = tag.match(/\.([a-z_\-][a-z0-9_\-]*)/gi);
        ids = tag.match(/\#([a-z_\-][a-z0-9_\-]*)/gi);
        tag = tag.match(/\%([a-z_\-][a-z0-9_:\-]*)/gi);

        // Default to
 tag
        tag = tag ? tag[0].substr(1, tag[0].length) : 'div';

        attribs = this.matches[3];
        if (attribs) {
          attribs = parse_attribs(attribs);
          if (attribs._content) {
            this.contents.unshift(attribs._content.trim());
            delete(attribs._content);
          }
        } else {
          attribs = {};
        }

        if (classes) {
          classes = classes.map(function (klass) {
            return klass.substr(1, klass.length);
          }).join(' ');
          if (attribs['class']) {
            try {
              attribs['class'] = JSON.stringify(classes + " " + JSON.parse(attribs['class']));
            } catch (e) {
              attribs['class'] = JSON.stringify(classes + " ") + " + " + attribs['class'];
            }
          } else {
            attribs['class'] = JSON.stringify(classes);
          }
        }
        if (ids) {
          ids = ids.map(function (id) {
            return id.substr(1, id.length);
          }).join(' ');
          if (attribs.id) {
            attribs.id = JSON.stringify(ids + " ") + attribs.id;
          } else {
            attribs.id = JSON.stringify(ids);
          }
        }

        attribs = render_attribs(attribs);

        content = this.render_contents();
        if (content === '""') {
          content = '';
        }

        if (forceXML ? content.length > 0 : self_close_tags.indexOf(tag) == -1) {
          return '"<' + tag + attribs + '>"' +
            (content.length > 0 ? ' + \n' + content : "") +
            ' + \n"</' + tag + '>"';
        } else {
          return '"<' + tag + attribs + ' />"';
        }
      }
    },

    // each loops
    {
      regexp: /^(\s*)(?::for|:each)\s+(?:([a-z_][a-z_\-]*),\s*)?([a-z_][a-z_\-]*)\s+in\s+(.*)(\s*)$/i,
      process: function () {
        var ivar = this.matches[2] || '__key__', // index
            vvar = this.matches[3],              // value
            avar = this.matches[4],              // array
            rvar = '__result__';                 // results

        if (this.matches[5]) {
          this.contents.unshift(this.matches[5]);
        }
        return '(function () { ' +
          'var ' + rvar + ' = [], ' + ivar + ', ' + vvar + '; ' +
          'for (' + ivar + ' in ' + avar + ') { ' +
          'if (' + avar + '.hasOwnProperty(' + ivar + ')) { ' +
          vvar + ' = ' + avar + '[' + ivar + ']; ' +
          rvar + '.push(\n' + (this.render_contents() || "''") + '\n); ' +
          '} } return ' + rvar + '.join(""); }).call(this)';
      }
    },

    // if statements
    {
      regexp: /^(\s*):if\s+(.*)\s*$/i,
      process: function () {
        var condition = this.matches[2];
        return '(function () { ' +
          'if (' + condition + ') { ' +
          'return (\n' + (this.render_contents() || '') + '\n);' +
          '} else { return ""; } }).call(this)';
      }
    },

    // declarations
    {
      regexp: /^()!!!(?:\s*(.*))\s*$/,
      process: function () {
        var line = '';
        switch ((this.matches[2] || '').toLowerCase()) {
        case '':
          // XHTML 1.0 Transitional
          line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
          break;
        case 'strict':
        case '1.0':
          // XHTML 1.0 Strict
          line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
          break;
        case 'frameset':
          // XHTML 1.0 Frameset
          line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">';
          break;
        case '5':
          // XHTML 5
          line = '<!DOCTYPE html>';
          break;
        case '1.1':
          // XHTML 1.1
          line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
          break;
        case 'basic':
          // XHTML Basic 1.1
          line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">';
          break;
        case 'mobile':
          // XHTML Mobile 1.2
          line = '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">';
          break;
        case 'xml':
          // XML
          line = "<?xml version='1.0' encoding='utf-8' ?>";
          break;
        case 'xml iso-8859-1':
          // XML iso-8859-1
          line = "<?xml version='1.0' encoding='iso-8859-1' ?>";
          break;
        }
        return JSON.stringify(line + "\n");
      }
    },

    // Embedded markdown. Needs to be added to exports externally.
    {
      regexp: /^(\s*):markdown\s*$/i,
      process: function () {
        return parse_interpol(exports.Markdown.encode(this.contents.join("\n")));
      }
    },

    // script blocks
    {
      regexp: /^(\s*):(?:java)?script\s*$/,
      process: function () {
        return parse_interpol('\n\n");
      }
    },

    // css blocks
    {
      regexp: /^(\s*):css\s*$/,
      process: function () {
        return JSON.stringify('\n\n");
      }
    },

  ];

  function compile(lines) {
    var block = false,
        output = [];

    // If lines is a string, turn it into an array
    if (typeof lines === 'string') {
      lines = lines.trim().replace(/\n\r|\r/g, '\n').split('\n');
    }

    lines.forEach(function(line) {
      var match, found = false;

      // Collect all text as raw until outdent
      if (block) {
        match = block.check_indent(line);
        if (match) {
          block.contents.push(match[1] || "");
          return;
        } else {
          output.push(block.process());
          block = false;
        }
      }

      matchers.forEach(function (matcher) {
        if (!found) {
          match = matcher.regexp(line);
          if (match) {
            block = {
              contents: [],
              matches: match,
              check_indent: new RegExp("^(?:\\s*|" + match[1] + "  (.*))$"),
              process: matcher.process,
              render_contents: function () {
                return compile(this. contents);
              }
            };
            found = true;
          }
        }
      });

      // Match plain text
      if (!found) {
        output.push(function () {
          // Escaped plain text
          if (line[0] === '\\') {
            return parse_interpol(line.substr(1, line.length));
          }

          // Plain variable data
          if (line[0] === '=') {
            line = line.substr(1, line.length).trim();
            try {
              return parse_interpol(JSON.parse(line));
            } catch (e) {
              return line;
            }
          }

          // HTML escape variable data
          if (line.substr(0, 2) === "&=") {
            line = line.substr(2, line.length).trim();
            try {
              return JSON.stringify(html_escape(JSON.parse(line)));
            } catch (e2) {
              return 'html_escape(' + line + ')';
            }
          }

          // Plain text
          return parse_interpol(line);
        }());
      }

    });
    if (block) {
      output.push(block.process());
    }
    return output.filter(function (part) { return part && part.length > 0}).join(" +\n");
  };

  function optimize(js) {
    var new_js = [], buffer = [], part, end;

    function flush() {
      if (buffer.length > 0) {
        new_js.push(JSON.stringify(buffer.join("")) + end);
        buffer = [];
      }
    }
    js.replace(/\n\r|\r/g, '\n').split('\n').forEach(function (line) {
      part = line.match(/^(\".*\")(\s*\+\s*)?$/);
      if (!part) {
        flush();
        new_js.push(line);
        return;
      }
      end = part[2] || "";
      part = part[1];
      try {
        buffer.push(JSON.parse(part));
      } catch (e) {
        flush();
        new_js.push(line);
      }
    });
    flush();
    return new_js.join("\n");
  };

  function render(text, options) {
    options = options || {};
    text = text || "";
    var js = compile(text);
    if (options.optimize) {
      js = Haml.optimize(js);
    }
    return execute(js, options.context || Haml, options.locals);
  };

  function execute(js, self, locals) {
    return (function () {
      with(locals || {}) {
        try {
          return eval("(" + js + ")");
        } catch (e) {
          return "\n
" + html_escape(e.stack) + "
\n";
        }

      }
    }).call(self);
  };

  Haml = function Haml(haml, xml) {
    forceXML = xml;
    var js = optimize(compile(haml));
    return new Function("locals",
      html_escape + "\n" +
      "with(locals || {}) {\n" +
      "  try {\n" +
      "    return (" + js + ");\n" +
      "  } catch (e) {\n" +
      "    return \"\\n
\" + html_escape(e.stack) + \"
\\n\";\n" +
      "  }\n" +
      "}");
  }
  Haml.compile = compile;
  Haml.optimize = optimize;
  Haml.render = render;
  Haml.execute = execute;

}());

// Hook into module system
if (typeof module !== 'undefined') {
  module.exports = Haml;
}
<commit_msg>Closes #26 when used in conjunction with compatibility methods for various core classes<commit_after>var Haml;

(function () {

  var matchers, self_close_tags, embedder, forceXML;

  function html_escape(text) {
    return (text + "").
      replace(/&/g, "&").
      replace(/</g, "<").
      replace(/>/g, ">").
      replace(/\"/g, """);
  }

  function render_attribs(attribs) {
    var key, value, result = [];
    for (key in attribs) {
      if (key !== '_content' && attribs.hasOwnProperty(key)) {
        switch (attribs[key]) {
        case 'undefined':
        case 'false':
        case 'null':
        case '""':
          break;
        default:
          try {
            value = JSON.parse("[" + attribs[key] +"]")[0];
            if (value === true) {
              value = key;
            } else if (typeof value === 'string' && embedder.test(value)) {
              value = '" +\n' + parse_interpol(html_escape(value)) + ' +\n"';
            } else {
              value = html_escape(value);
            }
            result.push(" " + key + '=\\"' + value + '\\"');
          } catch (e) {
            result.push(" " + key + '=\\"" + html_escape(' + attribs[key] + ') + "\\"');
          }
        }
      }
    }
    return result.join("");
  }

  // Parse the attribute block using a state machine
  function parse_attribs(line) {
    var attributes = {},
        l = line.length,
        i, c,
        count = 1,
        quote = false,
        skip = false,
        open, close, joiner, seperator,
        pair = {
          start: 1,
          middle: null,
          end: null
        };

    if (!(l > 0 && (line.charAt(0) === '{' || line.charAt(0) === '('))) {
      return {
        _content: line[0] === ' ' ? line.substr(1, l) : line
      };
    }
    open = line.charAt(0);
    close = (open === '{') ? '}' : ')';
    joiner = (open === '{') ? ':' : '=';
    seperator = (open === '{') ? ',' : ' ';

    function process_pair() {
      if (typeof pair.start === 'number' &&
          typeof pair.middle === 'number' &&
          typeof pair.end === 'number') {
        var key = line.substr(pair.start, pair.middle - pair.start).trim(),
            value = line.substr(pair.middle + 1, pair.end - pair.middle - 1).trim();
        attributes[key] = value;
      }
      pair = {
        start: null,
        middle: null,
        end: null
      };
    }

    for (i = 1; count > 0; i += 1) {

      // If we reach the end of the line, then there is a problem
      if (i > l) {
        throw "Malformed attribute block";
      }

      c = line.charAt(i);
      if (skip) {
        skip = false;
      } else {
        if (quote) {
          if (c === '\\') {
            skip = true;
          }
          if (c === quote) {
            quote = false;
          }
        } else {
          if (c === '"' || c === "'") {
            quote = c;
          }

          if (count === 1) {
            if (c === joiner) {
              pair.middle = i;
            }
            if (c === seperator || c === close) {
              pair.end = i;
              process_pair();
              if (c === seperator) {
                pair.start = i + 1;
              }
            }
          }

          if (c === open || c === "(") {
            count += 1;
          }
          if (c === close || (count > 1 && c === ")")) {
            count -= 1;
          }
        }
      }
    }
    attributes._content = line.substr(i, line.length);
    return attributes;
  }

  // Split interpolated strings into an array of literals and code fragments.
  function parse_interpol(value) {
    var items = [],
        pos = 0,
        next = 0,
        match;
    while (true) {
      // Match up to embedded string
      next = value.substr(pos).search(embedder);
      if (next < 0) {
        if (pos < value.length) {
          items.push(JSON.stringify(value.substr(pos)));
        }
        break;
      }
      items.push(JSON.stringify(value.substr(pos, next)));
      pos += next;

      // Match embedded string
      match = value.substr(pos).match(embedder);
      next = match[0].length;
      if (next < 0) { break; }
      items.push(match[1] || match[2]);
      pos += next;
    }
    return items.filter(function (part) { return part && part.length > 0}).join(" +\n");
  }

  // Used to find embedded code in interpolated strings.
  embedder = /\#\{([^}]*)\}/;

  self_close_tags = ["meta", "img", "link", "br", "hr", "input", "area", "base"];

  // All matchers' regexps should capture leading whitespace in first capture
  // and trailing content in last capture
  matchers = [
    // html tags
    {
      regexp: /^(\s*)((?:[.#%][a-z_\-][a-z0-9_:\-]*)+)(.*)$/i,
      process: function () {
        var tag, classes, ids, attribs, content;
        tag = this.matches[2];
        classes = tag.match(/\.([a-z_\-][a-z0-9_\-]*)/gi);
        ids = tag.match(/\#([a-z_\-][a-z0-9_\-]*)/gi);
        tag = tag.match(/\%([a-z_\-][a-z0-9_:\-]*)/gi);

        // Default to
 tag
        tag = tag ? tag[0].substr(1, tag[0].length) : 'div';

        attribs = this.matches[3];
        if (attribs) {
          attribs = parse_attribs(attribs);
          if (attribs._content) {
            this.contents.unshift(attribs._content.trim());
            delete(attribs._content);
          }
        } else {
          attribs = {};
        }

        if (classes) {
          classes = classes.map(function (klass) {
            return klass.substr(1, klass.length);
          }).join(' ');
          if (attribs['class']) {
            try {
              attribs['class'] = JSON.stringify(classes + " " + JSON.parse(attribs['class']));
            } catch (e) {
              attribs['class'] = JSON.stringify(classes + " ") + " + " + attribs['class'];
            }
          } else {
            attribs['class'] = JSON.stringify(classes);
          }
        }
        if (ids) {
          ids = ids.map(function (id) {
            return id.substr(1, id.length);
          }).join(' ');
          if (attribs.id) {
            attribs.id = JSON.stringify(ids + " ") + attribs.id;
          } else {
            attribs.id = JSON.stringify(ids);
          }
        }

        attribs = render_attribs(attribs);

        content = this.render_contents();
        if (content === '""') {
          content = '';
        }

        if (forceXML ? content.length > 0 : self_close_tags.indexOf(tag) == -1) {
          return '"<' + tag + attribs + '>"' +
            (content.length > 0 ? ' + \n' + content : "") +
            ' + \n"</' + tag + '>"';
        } else {
          return '"<' + tag + attribs + ' />"';
        }
      }
    },

    // each loops
    {
      regexp: /^(\s*)(?::for|:each)\s+(?:([a-z_][a-z_\-]*),\s*)?([a-z_][a-z_\-]*)\s+in\s+(.*)(\s*)$/i,
      process: function () {
        var ivar = this.matches[2] || '__key__', // index
            vvar = this.matches[3],              // value
            avar = this.matches[4],              // array
            rvar = '__result__';                 // results

        if (this.matches[5]) {
          this.contents.unshift(this.matches[5]);
        }
        return '(function () { ' +
          'var ' + rvar + ' = [], ' + ivar + ', ' + vvar + '; ' +
          'for (' + ivar + ' in ' + avar + ') { ' +
          'if (' + avar + '.hasOwnProperty(' + ivar + ')) { ' +
          vvar + ' = ' + avar + '[' + ivar + ']; ' +
          rvar + '.push(\n' + (this.render_contents() || "''") + '\n); ' +
          '} } return ' + rvar + '.join(""); }).call(this)';
      }
    },

    // if statements
    {
      regexp: /^(\s*):if\s+(.*)\s*$/i,
      process: function () {
        var condition = this.matches[2];
        return '(function () { ' +
          'if (' + condition + ') { ' +
          'return (\n' + (this.render_contents() || '') + '\n);' +
          '} else { return ""; } }).call(this)';
      }
    },

    // declarations
    {
      regexp: /^()!!!(?:\s*(.*))\s*$/,
      process: function () {
        var line = '';
        switch ((this.matches[2] || '').toLowerCase()) {
        case '':
          // XHTML 1.0 Transitional
          line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
          break;
        case 'strict':
        case '1.0':
          // XHTML 1.0 Strict
          line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
          break;
        case 'frameset':
          // XHTML 1.0 Frameset
          line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">';
          break;
        case '5':
          // XHTML 5
          line = '<!DOCTYPE html>';
          break;
        case '1.1':
          // XHTML 1.1
          line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
          break;
        case 'basic':
          // XHTML Basic 1.1
          line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">';
          break;
        case 'mobile':
          // XHTML Mobile 1.2
          line = '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">';
          break;
        case 'xml':
          // XML
          line = "<?xml version='1.0' encoding='utf-8' ?>";
          break;
        case 'xml iso-8859-1':
          // XML iso-8859-1
          line = "<?xml version='1.0' encoding='iso-8859-1' ?>";
          break;
        }
        return JSON.stringify(line + "\n");
      }
    },

    // Embedded markdown. Needs to be added to exports externally.
    {
      regexp: /^(\s*):markdown\s*$/i,
      process: function () {
        return parse_interpol(exports.Markdown.encode(this.contents.join("\n")));
      }
    },

    // script blocks
    {
      regexp: /^(\s*):(?:java)?script\s*$/,
      process: function () {
        return parse_interpol('\n\n");
      }
    },

    // css blocks
    {
      regexp: /^(\s*):css\s*$/,
      process: function () {
        return JSON.stringify('\n\n");
      }
    },

  ];

  function compile(lines) {
    var block = false,
        output = [];

    // If lines is a string, turn it into an array
    if (typeof lines === 'string') {
      lines = lines.trim().replace(/\n\r|\r/g, '\n').split('\n');
    }

    lines.forEach(function(line) {
      var match, found = false;

      // Collect all text as raw until outdent
      if (block) {
        match = block.check_indent.exec(line);
        if (match) {
          block.contents.push(match[1] || "");
          return;
        } else {
          output.push(block.process());
          block = false;
        }
      }

      matchers.forEach(function (matcher) {
        if (!found) {
          match = matcher.regexp.exec(line);
          if (match) {
            block = {
              contents: [],
              matches: match,
              check_indent: new RegExp("^(?:\\s*|" + match[1] + "  (.*))$"),
              process: matcher.process,
              render_contents: function () {
                return compile(this. contents);
              }
            };
            found = true;
          }
        }
      });

      // Match plain text
      if (!found) {
        output.push(function () {
          // Escaped plain text
          if (line[0] === '\\') {
            return parse_interpol(line.substr(1, line.length));
          }

          // Plain variable data
          if (line[0] === '=') {
            line = line.substr(1, line.length).trim();
            try {
              return parse_interpol(JSON.parse(line));
            } catch (e) {
              return line;
            }
          }

          // HTML escape variable data
          if (line.substr(0, 2) === "&=") {
            line = line.substr(2, line.length).trim();
            try {
              return JSON.stringify(html_escape(JSON.parse(line)));
            } catch (e2) {
              return 'html_escape(' + line + ')';
            }
          }

          // Plain text
          return parse_interpol(line);
        }());
      }

    });
    if (block) {
      output.push(block.process());
    }
    return output.filter(function (part) { return part && part.length > 0}).join(" +\n");
  };

  function optimize(js) {
    var new_js = [], buffer = [], part, end;

    function flush() {
      if (buffer.length > 0) {
        new_js.push(JSON.stringify(buffer.join("")) + end);
        buffer = [];
      }
    }
    js.replace(/\n\r|\r/g, '\n').split('\n').forEach(function (line) {
      part = line.match(/^(\".*\")(\s*\+\s*)?$/);
      if (!part) {
        flush();
        new_js.push(line);
        return;
      }
      end = part[2] || "";
      part = part[1];
      try {
        buffer.push(JSON.parse(part));
      } catch (e) {
        flush();
        new_js.push(line);
      }
    });
    flush();
    return new_js.join("\n");
  };

  function render(text, options) {
    options = options || {};
    text = text || "";
    var js = compile(text);
    if (options.optimize) {
      js = Haml.optimize(js);
    }
    return execute(js, options.context || Haml, options.locals);
  };

  function execute(js, self, locals) {
    return (function () {
      with(locals || {}) {
        try {
          return eval("(" + js + ")");
        } catch (e) {
          return "\n
" + html_escape(e.stack) + "
\n";
        }

      }
    }).call(self);
  };

  Haml = function Haml(haml, xml) {
    forceXML = xml;
    var js = optimize(compile(haml));
    return new Function("locals",
      html_escape + "\n" +
      "with(locals || {}) {\n" +
      "  try {\n" +
      "    return (" + js + ");\n" +
      "  } catch (e) {\n" +
      "    return \"\\n
\" + html_escape(e.stack) + \"
\\n\";\n" +
      "  }\n" +
      "}");
  }
  Haml.compile = compile;
  Haml.optimize = optimize;
  Haml.render = render;
  Haml.execute = execute;

}());

// Hook into module system
if (typeof module !== 'undefined') {
  module.exports = Haml;
}
<|endoftext|>


<commit_before>/**
 * @chill- Even as the dense and solid rock Cannot be stirred by either wind or storm: Even so the wise cannot be moved By voices of blame or voices of praise - Buddha
 * @name- NewBadlee
 */

"use strict";
import React, { Component } from "react";
import { connect } from "react-redux";
import { Image } from "react-native";
import {
  StyleProvider,
  Container,
  Header,
  Left,
  Right,
  Text,
  Content,
  Form,
  View,
  Button,
  Input,
  Icon as IconX,
  Item
} from "native-base";
import ImagePicker from "react-native-image-picker";

import getTheme from "../../theme/components";
import Icon from "../../components/Icon";
import * as actionCreators from "../../badlee__redux/action_creators";

import Main from "./Container";
import LoadingView from "../../components/LoadingView";

class NewBadlee extends Component {
  constructor(props) {
    super(props);
    this.state = {
      badleePhotoSource: null,
      badleePhotoName: null,
      badleePhotoType: null,
      description: null,
      ip: "11.12.13.14",
      location: null,
      purpose: this.props.params.type ? this.props.params.type : null,
      category: null
    };
  }
  backPress() {
    requestAnimationFrame(() => {
      this.props.navigate({
        navigator: this.props.navigator,
        component: Main
      });
    });
  }
  selectPhotoTapped() {
    const options = {
      quality: "1.0",
      maxWidth: 500,
      maxHeight: 500,
      storageOptions: {
        skipBackup: true
      }
    };
    ImagePicker.showImagePicker(options, response => {
      console.log("Response = ", response);
      console.log(response.didCancel);
      console.log(response.error);
      console.log(response.customButton);
      if (response.didCancel) {
        console.log("user cancelled photo picker");
      } else if (response.error) {
        console.log("Imagepicker error : ", response.error);
      } else if (response.customButton) {
        console.log("user tapped custom button : ", response.customButton);
      } else {
        console.log(response);
        let source = { uri: response.uri };
        this.setState({
          badleePhotoSource: source,
          badleePhotoName: response.fileName,
          badleePhotoType: response.type
        });
      }
    });
  }
  saveBadlee() {
    requestAnimationFrame(() => {
      this.props.saveBadlee(
        this.state.badleePhoto,
        this.state.description,
        this.state.ip,
        this.state.location,
        this.state.purpose,
        this.state.category,
        {
          navigator: this.props.navigator,
          component: Main,
          reset: true
        }
      );
    });
  }
  render() {
    return (
      <StyleProvider style={getTheme()}>
        <Container>
          {this.props.loading && <LoadingView />}
          <Header style={{ backgroundColor: "#611265" }}>
            <Left>
              <Button transparent onPress={this.backPress.bind(this)}>
                <IconX name="arrow-back" />
              </Button>
            </Left>
            <Right>
              <Button transparent onPress={this.saveBadlee.bind(this)}>
                <Text style={{ color: "#fff", fontSize: 15 }}>POST</Text>
              </Button>
            </Right>
          </Header>
          <Content style={{ flex: 1 }} contentContainerStyle={{ flex: 1 }}>
            <Form style={{ flex: 1, display: "flex" }}>
              <View style={styles.badleePhotoWrapper}>
                {this.state.badleePhotoSource &&
                  <Image
                    style={styles.avatar}
                    source={this.state.badleePhotoSource}
                  />}
                {!this.state.badleePhotoSource &&
                  <Button
                    transparent
                    onPress={this.selectPhotoTapped.bind(this)}
                    style={{
                      width: 160,
                      height: 120,
                      marginLeft: "auto",
                      marginRight: "auto"
                    }}
                  >
                    <Icon name="userPlaceholder" width="120" height="120" />
                  </Button>}
                <Text>Hello</Text>
              </View>
              <Item>
                <Input
                  placeholder="Title.."
                  value={this.state.title}
                  onChangeText={title => this.setState({ title })}
                />
              </Item>
              <Item>
                <Input
                  placeholder="Write a Caption"
                  value={this.state.description}
                  onChangeText={description => this.setState({ description })}
                />
              </Item>
              <Item>
                <Input
                  placeholder="Where is it?"
                  value={this.state.location}
                  onChangeText={location => this.setState({ location })}
                />
              </Item>
              <Item>
                <Input
                  placeholder="Purpose"
                  disabled
                  value={this.state.purpose}
                />
              </Item>
              <Item>
                <Input
                  placeholder="Where does it fits?"
                  value={this.state.category}
                  onChangeText={category => this.setState({ category })}
                />
              </Item>
            </Form>
          </Content>
        </Container>
      </StyleProvider>
    );
  }
}

var styles = {
  badleePhotoWrapper: {
    paddingTop: 6,
    paddingBottom: 6,
    flex: 1
  },
  avatar: {
    width: 160,
    height: 160,
    marginLeft: "auto",
    marginRight: "auto"
  }
};

const _Wrapped = connect(
  state => ({ loading: state.get("isLoading") }),
  actionCreators
)(NewBadlee);

export default _Wrapped;
<commit_msg>deleted stupid file<commit_after><|endoftext|>