pax_global_header 0000666 0000000 0000000 00000000064 14717402212 0014512 g ustar 00root root 0000000 0000000 52 comment=7b50a1bbb5324838908dfaa00ec53ad322673a29
sinatra-4.1.1/ 0000775 0000000 0000000 00000000000 14717402212 0013156 5 ustar 00root root 0000000 0000000 sinatra-4.1.1/.github/ 0000775 0000000 0000000 00000000000 14717402212 0014516 5 ustar 00root root 0000000 0000000 sinatra-4.1.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14717402212 0016553 5 ustar 00root root 0000000 0000000 sinatra-4.1.1/.github/workflows/CODEOWNERS 0000664 0000000 0000000 00000000036 14717402212 0020145 0 ustar 00root root 0000000 0000000 * @sinatra/team-sinatra
sinatra-4.1.1/.github/workflows/release.yml 0000664 0000000 0000000 00000001472 14717402212 0020722 0 ustar 00root root 0000000 0000000 name: Release
on:
push:
tags:
- v*
workflow_dispatch:
jobs:
release:
if: github.repository == 'sinatra/sinatra'
runs-on: ubuntu-latest
permissions:
id-token: write # for trusted publishing
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
ruby-version: ruby
- uses: rubygems/configure-rubygems-credentials@v1.0.0
# ensure gems can be built and installed
- run: bundle exec rake install:rack-protection
- run: bundle exec rake install:sinatra
- run: bundle exec rake install:sinatra-contrib
# push gems to rubygems.org
- run: bundle exec rake release:rack-protection
- run: bundle exec rake release:sinatra
- run: bundle exec rake release:sinatra-contrib
sinatra-4.1.1/.github/workflows/test.yml 0000664 0000000 0000000 00000014177 14717402212 0020267 0 ustar 00root root 0000000 0000000 name: Testing
on:
push:
branches:
- '**'
pull_request:
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
rack-protection:
name: rack-protection (${{ matrix.ruby }}, rack ${{ matrix.rack }})
runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
fail-fast: false
matrix:
rack:
- stable
ruby:
- "2.7"
- "3.0"
- "3.1"
- "3.2"
- "3.3"
- "jruby"
- "truffleruby"
include:
# Rack
- { ruby: 3.2, rack: "~>3.0.0" }
- { ruby: 3.2, rack: head }
# Never fail our build due to problems with Ruby head
- { ruby: ruby-head, rack: stable, allow-failure: true }
env:
rack: ${{ matrix.rack }}
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
continue-on-error: ${{ matrix.allow-failure || false }}
id: setup-ruby
with:
bundler-cache: true
ruby-version: ${{ matrix.ruby }}
working-directory: rack-protection
- name: Run rack-protection tests
continue-on-error: ${{ matrix.allow-failure || false }}
id: protection-tests
working-directory: rack-protection
run: |
bundle exec rake
# because continue-on-error marks the steps as pass even if they fail
- name: "setup-ruby (bundle install) outcome: ${{ steps.setup-ruby.outcome }}"
run: ""
- name: "rack-protection tests outcome: ${{ steps.protection-tests.outcome }}"
run: ""
- uses: zzak/action-discord@v6
if: failure() && github.ref_name == 'main'
continue-on-error: true # always allow failure
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
webhook: ${{ secrets.DISCORD_WEBHOOK }}
sinatra:
name: |
${{ matrix.ruby }}
(Rack ${{ matrix.rack }}, Rack::Session ${{ matrix.rack_session }}, Puma ${{ matrix.puma }}, Tilt ${{ matrix.tilt }}, Zeitwerk ${{ matrix.zeitwerk }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
puma:
- stable
rack:
- stable
rack_session:
- stable
tilt:
- stable
zeitwerk:
- stable
ruby:
- "2.7"
- "3.0"
- "3.1"
- "3.2"
- "3.3"
rubyopt:
- "--enable-frozen-string-literal --debug-frozen-string-literal"
include:
# Rack
- { ruby: 3.2, rack: "~>3.0.0", puma: stable, tilt: stable, rack_session: stable, zeitwerk: stable }
- { ruby: 3.2, rack: head, puma: stable, tilt: stable, rack_session: stable, zeitwerk: stable }
# Rack::Session
- { ruby: 3.2, rack: stable, puma: stable, tilt: stable, rack_session: head, zeitwerk: stable }
# Puma
- { ruby: 3.2, rack: stable, puma: head, tilt: stable, rack_session: stable, zeitwerk: stable }
# Tilt
- { ruby: 3.2, rack: stable, puma: stable, tilt: head, rack_session: stable, zeitwerk: stable }
# Test Zeitwerk < 2.7.0 separately
- { ruby: 3.2, rack: stable, puma: stable, tilt: head, rack_session: stable, zeitwerk: '<2.7.0' }
# JRuby, tests are flaky: https://github.com/sinatra/sinatra/issues/2027
- { ruby: jruby, rubyopt: "", rack: stable, puma: stable, tilt: stable, rack_session: stable, zeitwerk: stable, allow-failure: true }
# Never fail our build due to problems with head rubies
- { ruby: ruby-head, rack: stable, puma: stable, tilt: stable, rack_session: stable, zeitwerk: stable, allow-failure: true }
- { ruby: jruby-head, rubyopt: "", rack: stable, puma: stable, tilt: stable, rack_session: stable, zeitwerk: stable, allow-failure: true }
- { ruby: truffleruby-head, rack: stable, puma: stable, tilt: stable, rack_session: stable, zeitwerk: stable, allow-failure: true }
# truffleruby 24 fails, see https://github.com/sinatra/sinatra/issues/2007
- { ruby: truffleruby, rack: stable, puma: stable, tilt: stable, rack_session: stable, zeitwerk: stable, allow-failure: true }
env:
rack: ${{ matrix.rack }}
rack_session: ${{ matrix.rack_session }}
puma: ${{ matrix.puma }}
tilt: ${{ matrix.tilt }}
zeitwerk: ${{ matrix.zeitwerk }}
RUBYOPT: "${{ matrix.rubyopt }}"
# need to unset RUBYOPT for JRuby: https://github.com/jruby/ruby-maven/issues/12
steps:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install --yes \
pandoc \
nodejs \
pkg-config \
libxml2-dev \
libxslt-dev \
libyaml-dev
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
continue-on-error: ${{ matrix.allow-failure || false }}
id: setup-ruby
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
# Update rubygems due to https://github.com/rubygems/rubygems/pull/6490 (3.0)
# and https://github.com/sinatra/sinatra/issues/2051 (3.1)
rubygems: ${{ matrix.ruby == '3.0' && 'latest' || matrix.ruby == '3.1' && 'latest' || 'default' }}
- name: Run sinatra tests
continue-on-error: ${{ matrix.allow-failure || false }}
id: tests
run: bundle exec rake
- name: Run sinatra-contrib tests
continue-on-error: ${{ matrix.allow-failure || false }}
id: contrib-tests
working-directory: sinatra-contrib
run: |
bundle install --jobs=3 --retry=3
bundle exec rake
# because continue-on-error marks the steps as pass even if they fail
- name: "setup-ruby (bundle install) outcome: ${{ steps.setup-ruby.outcome }}"
run: ""
- name: "sinatra tests outcome: ${{ steps.tests.outcome }}"
run: ""
- name: "sinatra-contrib tests outcome: ${{ steps.contrib-tests.outcome }}"
run: ""
- uses: zzak/action-discord@v6
if: failure() && github.ref_name == 'main'
continue-on-error: true # always allow failure
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
webhook: ${{ secrets.DISCORD_WEBHOOK }}
sinatra-4.1.1/.gitignore 0000664 0000000 0000000 00000000270 14717402212 0015145 0 ustar 00root root 0000000 0000000 # please add general patterns to your global ignore list
# see https://github.com/github/gitignore#readme
.DS_STORE
*.swp
*.rbc
/pkg
*.lock
/coverage
.yardoc
/doc
.bundle
vendor
*.gem
sinatra-4.1.1/.rubocop.yml 0000664 0000000 0000000 00000005142 14717402212 0015432 0 ustar 00root root 0000000 0000000 # The behavior of RuboCop can be controlled via the .rubocop.yml
# configuration file. It makes it possible to enable/disable
# certain cops (checks) and to alter their behavior if they accept
# any parameters. The file can be placed either in your home
# directory or in some project directory.
#
# RuboCop will start looking for the configuration file in the directory
# where the inspected file is and continue its way up to the root directory.
#
# See https://github.com/rubocop-hq/rubocop/blob/master/manual/configuration.md
AllCops:
TargetRubyVersion: 2.7
SuggestExtensions: false
NewCops: enable
Exclude:
- 'test/**/*'
- 'rack-protection/**/*'
- 'sinatra-contrib/**/*'
- vendor/bundle/**/*
Layout/ExtraSpacing:
AllowForAlignment: true
AllowBeforeTrailingComments: true
# Temporary disable cops because warnings are fixed
Style/SingleLineMethods:
Enabled: false
Style/MutableConstant:
Enabled: false
Lint/AmbiguousBlockAssociation:
Enabled: false
Style/CaseEquality:
Enabled: false
Style/PerlBackrefs:
Enabled: false
Style/Documentation:
Enabled: false
Lint/IneffectiveAccessModifier:
Enabled: false
Lint/RescueException:
Enabled: false
Style/SpecialGlobalVars:
Enabled: false
Bundler/DuplicatedGem:
Enabled: false
Layout/HeredocIndentation:
Enabled: false
Style/FormatStringToken:
Enabled: false
Lint/UselessAccessModifier:
Enabled: false
Style/ClassVars:
Enabled: false
Lint/UselessAssignment:
Enabled: false
Style/EmptyLiteral:
Enabled: false
Layout/LineLength:
Enabled: false
Metrics/MethodLength:
Enabled: false
Metrics/AbcSize:
Enabled: false
Metrics/CyclomaticComplexity:
Enabled: false
Metrics/PerceivedComplexity:
Enabled: false
Lint/SuppressedException:
Enabled: false
Metrics/ClassLength:
Enabled: false
Metrics/BlockLength:
Enabled: false
Metrics/ModuleLength:
Enabled: false
Lint/AmbiguousRegexpLiteral:
Enabled: false
Style/AccessModifierDeclarations:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
Style/EvalWithLocation:
Enabled: false
Lint/MissingSuper:
Enabled: false
Style/MissingRespondToMissing:
Enabled: false
Style/MixinUsage:
Enabled: false
Style/MultilineTernaryOperator:
Enabled: false
Style/StructInheritance:
Enabled: false
Style/SymbolProc:
Enabled: false
Style/IfUnlessModifier:
Enabled: false
Style/OptionalBooleanParameter:
Enabled: false
Style/DocumentDynamicEvalDefinition:
Enabled: false
Lint/ToEnumArguments:
Enabled: false
Naming/MethodParameterName:
Enabled: false
Naming/AccessorMethodName:
Enabled: false
Style/SlicingWithRange:
Enabled: false
sinatra-4.1.1/.yardopts 0000664 0000000 0000000 00000000160 14717402212 0015021 0 ustar 00root root 0000000 0000000 --readme README.md
--title 'Sinatra API Documentation'
--charset utf-8
--markup markdown
'lib/**/*.rb' - '*.md'
sinatra-4.1.1/AUTHORS.md 0000664 0000000 0000000 00000007366 14717402212 0014641 0 ustar 00root root 0000000 0000000 Sinatra was designed and developed by Blake Mizerany in California.
### Current Team
* **Eloy Perez**
* **Jordan Owens**
* **Olle Jonsson**
* **Patrik Ragnarsson**
* **Zachary Scott**
### Alumni
* **Blake Mizerany** (creator)
* **Konstantin Haase** (maintainer)
* **Ryan Tomayko**
* **Simon Rozet**
* **Katrina Owen**
* **Kashyap Kondamudi**
* **Ashley Williams**
* **Trevor Bramble**
* **Kunpei Sakai**
### Thanks
Sinatra would not have been possible without strong company backing.
In the past, financial and emotional support have been provided mainly by
[Heroku](https://heroku.com), [GitHub](https://github.com),
[Engine Yard](http://www.engineyard.com/) and [Travis CI](https://travis-ci.com/),
and is now taken care of by [84codes](https://www.84codes.com/).
Special thanks to the following extraordinary individuals, without whom
Sinatra would not be possible:
* [Benoit Daloze](https://eregon.me/blog/) (eregon) for help around TruffleRuby
and keyword arguments use in mustermann.
* [Ryan Tomayko](http://tomayko.com/) (rtomayko) for constantly fixing
whitespace errors __60d5006__
* [Ezra Zygmuntowicz](http://brainspl.at/) (ezmobius) for initial help and
letting Blake steal some of merbs internal code.
* [Chris Schneider](http://gittr.com) (cschneid) for The Book, the blog,
[irclogger.com](http://irclogger.com/sinatra/), and a bunch of useful
patches.
* [Markus Prinz](http://nuclearsquid.com/) (cypher) for patches over the
years, caring about the README, and hanging in there when times were rough.
* [Erik Kastner](http://metaatem.net/) (kastner) for fixing `MIME_TYPES` under
Rack 0.5.
* [Ben Bleything](http://blog.bleything.net/) (bleything) for caring about HTTP
status codes and doc fixes.
* [Igal Koshevoy](http://twitter.com/igalko) (igal) for root path detection under
Thin/Passenger.
* [Jon Crosby](http://joncrosby.me/) (jcrosby) for coffee breaks, doc fixes, and
just because, man.
* [Karel Minarik](https://github.com/karmi) (karmi) for screaming until the
website came back up.
* [Jeremy Evans](http://code.jeremyevans.net/) (jeremyevans) for unbreaking
optional path params (twice!)
* [The GitHub guys](https://github.com/) for stealing Blake's table.
* [Nickolas Means](http://nmeans.org/) (nmeans) for Sass template support.
* [Victor Hugo Borja](https://github.com/vic) (vic) for splat'n routes specs and
doco.
* [Avdi Grimm](http://avdi.org/) (avdi) for basic RSpec support.
* [Jack Danger Canty](http://jåck.com/) for a more accurate root directory
and for making me watch [this](http://www.youtube.com/watch?v=ueaHLHgskkw) just
now.
* Mathew Walker for making escaped paths work with static files.
* Millions of Us for having the problem that led to Sinatra's conception.
* [Songbird](http://getsongbird.com/) for the problems that helped Sinatra's
future become realized.
* [Rick Olson](http://techno-weenie.net/) (technoweenie) for the killer plug
at RailsConf '08.
* Steven Garcia for the amazing custom artwork you see on 404's and 500's
* [Pat Nakajima](http://patnakajima.com/) (nakajima) for fixing non-nested
params in nested params Hash's.
* Gabriel Andretta for having people wonder whether our documentation is
actually in English or in Spanish.
* Vasily Polovnyov, Nickolay Schwarz, Luciano Sousa, Wu Jiang,
Mickael Riga, Bernhard Essl, Janos Hardi, Kouhei Yanagita and
"burningTyger" for willingly translating whatever ends up in the README.
* [Wordy](https://wordy.com/) for proofreading our README. **73e137d**
* cactus for digging through code and specs, multiple times.
* Nicolás Sanguinetti (foca) for strong demand of karma and shaping
helpers/register.
And last but not least:
* [Frank Sinatra](http://www.sinatra.com/) (chairman of the board) for having so much class he
deserves a web-framework named after him.
sinatra-4.1.1/CHANGELOG.md 0000664 0000000 0000000 00000236143 14717402212 0015000 0 ustar 00root root 0000000 0000000 ## 4.1.1 / 2024-11-20
* Fix: Restore WEBrick support ([#2067](https://github.com/sinatra/sinatra/pull/2067))
## 4.1.0 / 2024-11-18
* New: Add `host_authorization` setting ([#2053](https://github.com/sinatra/sinatra/pull/2053))
* Defaults to `.localhost`, `.test` and any IP address in development mode.
* Security: addresses [CVE-2024-21510](https://github.com/advisories/GHSA-hxx2-7vcw-mqr3).
* Fix: Return an instance of `Sinatra::IndifferentHash` when calling `#except` ([#2044](https://github.com/sinatra/sinatra/pull/2044))
* Fix: Address warning from `URI` for Ruby 3.4 ([#2060](https://github.com/sinatra/sinatra/pull/2060))
* Fix: `rackup` no longer depends on WEBrick, recommend Puma instead ([`4a558503`](https://github.com/sinatra/sinatra/commit/4a558503a0ee41f26d4ebc07b478340e8a8a5ed6))
* Fix: Zeitwerk 2.7.0+ compatibility ([#2050](https://github.com/sinatra/sinatra/pull/2050))
* Fix: Address warning about Hash construction for Ruby 3.4 ([#2028](https://github.com/sinatra/sinatra/pull/2028))
* Fix: Declare missing dependencies for Ruby 3.5 ([#2032](https://github.com/sinatra/sinatra/pull/2032))
* Fix: Compatibility with `--enable-frozen-string-literal` ([#2033](https://github.com/sinatra/sinatra/pull/2033))
* Fix: Rack 3.1 compatibility ([#2035](https://github.com/sinatra/sinatra/pull/2035))
* Don't depend on `Rack::Logger`
* Don't delete `content-length` header when `Rack::Files` is used
## 4.0.0. / 2024-01-19
* New: Add support for Rack 3 ([#1857])
* Note: you may want to read the [Rack 3 Upgrade Guide]
* Require Ruby 2.7.8 as minimum Ruby version ([#1993])
* Breaking change: Drop support for Rack 2 ([#1857])
* Note: when using Sinatra to start the web server, you now need the `rackup` gem installed
* Breaking change: Remove the `IndifferentHash` initializer ([#1982])
* Breaking change: Disable `session_hijacking` protection by default ([#1984])
* Breaking change: Remove `Rack::Protection::EncryptedCookie` ([#1989])
* Note: cookies are still encrypted (by [`Rack::Session::Cookie`])
[#1857]: https://github.com/sinatra/sinatra/pull/1857
[#1993]: https://github.com/sinatra/sinatra/pull/1993
[#1982]: https://github.com/sinatra/sinatra/pull/1982
[#1984]: https://github.com/sinatra/sinatra/pull/1984
[#1989]: https://github.com/sinatra/sinatra/pull/1989
[`Rack::Session::Cookie`]: https://github.com/rack/rack-session
[Rack 3 Upgrade Guide]: https://github.com/rack/rack/blob/main/UPGRADE-GUIDE.md
## 3.2.0 / 2023-12-29
* New: Add `#except` method to `Sinatra::IndifferentHash` ([#1940])
* New: Use `Exception#detailed_message` to show backtrace ([#1952])
* New: Add `Sinatra::HamlHelpers` to sinatra-contrib ([#1960])
* Fix: Add `base64` to rack-protection runtime dependencies ([#1946])
* Fix: Avoid open-ended dependencies for sinatra-contrib and rack-protection ([#1949])
* Fix: Helpful message when `Sinatra::Runner` times out ([#1975])
* Fix: Ruby 3.3 + Bundler 2.5 compatibility ([#1975])
[#1940]: https://github.com/sinatra/sinatra/pull/1940
[#1946]: https://github.com/sinatra/sinatra/pull/1946
[#1949]: https://github.com/sinatra/sinatra/pull/1949
[#1952]: https://github.com/sinatra/sinatra/pull/1952
[#1960]: https://github.com/sinatra/sinatra/pull/1960
[#1975]: https://github.com/sinatra/sinatra/pull/1975
## 3.1.0 / 2023-08-07
* New: Add sass support via sass-embedded [#1911] by なつき
* New: Add start and stop callbacks [#1913] by Jevin Sew
* New: Warn on dropping sessions [#1900] by Jonathan del Strother
* New: Make Puma the default server [#1924] by Patrik Ragnarsson
* Fix: Remove use of Tilt::Cache [#1922] by Jeremy Evans (allows use of Tilt 2.2.0 without deprecation warning)
* Fix: rack-protection: specify rack version requirement [#1932] by Patrik Ragnarsson
[#1911]: https://github.com/sinatra/sinatra/pull/1911
[#1913]: https://github.com/sinatra/sinatra/pull/1913
[#1900]: https://github.com/sinatra/sinatra/pull/1900
[#1924]: https://github.com/sinatra/sinatra/pull/1924
[#1922]: https://github.com/sinatra/sinatra/pull/1922
[#1932]: https://github.com/sinatra/sinatra/pull/1932
## 3.0.6 / 2023-04-11
* Fix: Add support to keep open streaming connections with Puma [#1858](https://github.com/sinatra/sinatra/pull/1858) by Jordan Owens
* Fix: Avoid crash in `uri` helper on Integer input [#1890](https://github.com/sinatra/sinatra/pull/1890) by Patrik Ragnarsson
* Fix: Rescue `RuntimeError` when trying to use `SecureRandom` [#1888](https://github.com/sinatra/sinatra/pull/1888) by Stefan Sundin
## 3.0.5 / 2022-12-16
* Fix: Add Zeitwerk compatibility. [#1831](https://github.com/sinatra/sinatra/pull/1831) by Dawid Janczak
* Fix: Allow CALLERS_TO_IGNORE to be overridden
## 3.0.4 / 2022-11-25
* Fix: Escape filename in the Content-Disposition header. [#1841](https://github.com/sinatra/sinatra/pull/1841) by Kunpei Sakai
## 3.0.3 / 2022-11-11
* Fix: fixed ReDoS for Rack::Protection::IPSpoofing. [#1823](https://github.com/sinatra/sinatra/pull/1823) by @ooooooo-q
## 3.0.2 / 2022-10-01
* New: Add Haml 6 support. [#1820](https://github.com/sinatra/sinatra/pull/1820) by Jordan Owens
## 3.0.1 / 2022-09-26
* Fix: Revert removal of rack-protection.rb. [#1814](https://github.com/sinatra/sinatra/pull/1814) by Olle Jonsson
* Fix: Revert change to server start and stop messaging by using Kernel#warn. Renamed internal warn method warn_for_deprecation. [#1818](https://github.com/sinatra/sinatra/pull/1818) by Jordan Owens
## 3.0.0 / 2022-09-26
* New: Add Falcon support. [#1794](https://github.com/sinatra/sinatra/pull/1794) by Samuel Williams and @horaciob
* New: Add AES GCM encryption support for session cookies. [#1324] (https://github.com/sinatra/sinatra/pull/1324) by Michael Coyne
* Deprecated: Sinatra Reloader will be removed in the next major release.
* Fix: Internal Sinatra errors now extend `Sinatra::Error`. This fixes [#1204](https://github.com/sinatra/sinatra/issues/1204) and [#1518](https://github.com/sinatra/sinatra/issues/1518). [bda8c29d](https://github.com/sinatra/sinatra/commit/bda8c29d70619d53f5b1c181140638d340695514) by Jordan Owens
* Fix: Preserve query param value if named route param nil. [#1676](https://github.com/sinatra/sinatra/pull/1676) by Jordan Owens
* Require Ruby 2.6 as minimum Ruby version. [#1699](https://github.com/sinatra/sinatra/pull/1699) by Eloy Pérez
* Breaking change: Remove support for the Stylus template engine. [#1697](https://github.com/sinatra/sinatra/pull/1697) by Eloy Pérez
* Breaking change: Remove support for the erubis template engine. [#1761](https://github.com/sinatra/sinatra/pull/1761) by Eloy Pérez
* Breaking change: Remove support for the textile template engine. [#1766](https://github.com/sinatra/sinatra/pull/1766) by Eloy Pérez
* Breaking change: Remove support for SASS as a template engine. [#1768](https://github.com/sinatra/sinatra/pull/1768) by Eloy Pérez
* Breaking change: Remove support for Wlang as a template engine. [#1780](https://github.com/sinatra/sinatra/pull/1780) by Eloy Pérez
* Breaking change: Remove support for CoffeeScript as a template engine. [#1790](https://github.com/sinatra/sinatra/pull/1790) by Eloy Pérez
* Breaking change: Remove support for Mediawiki as a template engine. [#1791](https://github.com/sinatra/sinatra/pull/1791) by Eloy Pérez
* Breaking change: Remove support for Creole as a template engine. [#1792](https://github.com/sinatra/sinatra/pull/1792) by Eloy Pérez
* Breaking change: Remove support for Radius as a template engine. [#1793](https://github.com/sinatra/sinatra/pull/1793) by Eloy Pérez
* Breaking change: Remove support for the defunct Less templating library. See [#1716](https://github.com/sinatra/sinatra/issues/1716), [#1715](https://github.com/sinatra/sinatra/issues/1715) for more discussion and background. [d1af2f1e](https://github.com/sinatra/sinatra/commit/d1af2f1e6c8710419dfe3102a660f7a32f0e67e3) by Olle Jonsson
* Breaking change: Remove Reel integration. [54597502](https://github.com/sinatra/sinatra/commit/545975025927a27a1daca790598620038979f1c5) by Olle Jonsson
* CI: Start testing on Ruby 3.1. [60e221940](https://github.com/sinatra/sinatra/commit/60e2219407e6ae067bf3e53eb060ee4860c60c8d) and [b0fa4bef](https://github.com/sinatra/sinatra/commit/b0fa4beffaa3b10bf02947d0a35e137403296c6b) by Johannes Würbach
* Use `Kernel#caller_locations`. [#1491](https://github.com/sinatra/sinatra/pull/1491) by Julik Tarkhanov
* Docs: Japanese documentation: Add notes about the `default_content_type` setting. [#1650](https://github.com/sinatra/sinatra/pull/1650) by Akifumi Tominaga
* Docs: Polish documentation: Add section about Multithreaded modes and Routes. [#1708](https://github.com/sinatra/sinatra/pull/1708) by Patrick Gramatowski
* Docs: Japanese documentation: Make Session section reflect changes done to README.md. [#1731](https://github.com/sinatra/sinatra/pull/1731) by @shu-i-chi
## 2.2.3 / 2022-11-25
* Fix: Escape filename in the Content-Disposition header. [#1841](https://github.com/sinatra/sinatra/pull/1841) by Kunpei Sakai
* Fix: fixed ReDoS for Rack::Protection::IPSpoofing. [#1823](https://github.com/sinatra/sinatra/pull/1823) by @ooooooo-q
## 2.2.2 / 2022-07-23
* Update mustermann dependency to version 2.
## 2.2.1 / 2022-07-15
* Fix JRuby regression by using ruby2_keywords for delegation. #1750 by Patrik Ragnarsson
* Add JRuby to CI. #1755 by Karol Bucek
## 2.2.0 / 2022-02-15
* Breaking change: Add `#select`, `#reject` and `#compact` methods to `Sinatra::IndifferentHash`. If hash keys need to be converted to symbols, call `#to_h` to get a `Hash` instance first. [#1711](https://github.com/sinatra/sinatra/pull/1711) by Olivier Bellone
* Handle EOFError raised by Rack and return Bad Request 400 status. [#1743](https://github.com/sinatra/sinatra/pull/1743) by tamazon
* Minor refactors in `base.rb`. [#1640](https://github.com/sinatra/sinatra/pull/1640) by ceclinux
* Add escaping to the static 404 page. [#1645](https://github.com/sinatra/sinatra/pull/1645) by Chris Gavin
* Remove `detect_rack_handler` method. [#1652](https://github.com/sinatra/sinatra/pull/1652) by ceclinux
* Respect content type set in superclass before filter. Fixes [#1647](https://github.com/sinatra/sinatra/issues/1647) [#1649](https://github.com/sinatra/sinatra/pull/1649) by Jordan Owens
* *Revert "Use prepend instead of include for helpers.* [#1662](https://github.com/sinatra/sinatra/pull/1662) by namusyaka
* Fix usage of inherited `Sinatra::Base` classes keyword arguments. Fixes [#1669](https://github.com/sinatra/sinatra/issues/1669) [#1670](https://github.com/sinatra/sinatra/pull/1670) by Cadu Ribeiro
* Reduce RDoc generation time by not including every README. Fixes [#1578](https://github.com/sinatra/sinatra/issues/1578) [#1671](https://github.com/sinatra/sinatra/pull/1671) by Eloy Pérez
* Add support for per form csrf tokens. Fixes [#1616](https://github.com/sinatra/sinatra/issues/1616) [#1653](https://github.com/sinatra/sinatra/pull/1653) by Jordan Owens
* Update MAINTENANCE.md with the `stable` branch status. [#1681](https://github.com/sinatra/sinatra/pull/1681) by Fredrik Rubensson
* Validate expanded path matches `public_dir` when serving static files. [#1683](https://github.com/sinatra/sinatra/pull/1683) by cji-stripe
* Fix Delegator to pass keyword arguments for Ruby 3.0. [#1684](https://github.com/sinatra/sinatra/pull/1684) by andrewtblake
* Fix use with keyword arguments for Ruby 3.0. [#1701](https://github.com/sinatra/sinatra/pull/1701) by Robin Wallin
* Fix memory leaks for proc template. Fixes [#1704](https://github.com/sinatra/sinatra/issues/1714) [#1719](https://github.com/sinatra/sinatra/pull/1719) by Slevin
* Remove unnecessary `test_files` from the gemspec. [#1712](https://github.com/sinatra/sinatra/pull/1712) by Masataka Pocke Kuwabara
* Docs: Spanish documentation: Update README.es.md with removal of Thin. [#1630](https://github.com/sinatra/sinatra/pull/1630) by Espartaco Palma
* Docs: German documentation: Fixed typos in German README.md. [#1648](https://github.com/sinatra/sinatra/pull/1648) by Juri
* Docs: Japanese documentation: Update README.ja.md with removal of Thin. [#1629](https://github.com/sinatra/sinatra/pull/1629) by Ryuichi KAWAMATA
* Docs: English documentation: Various minor fixes to README.md. [#1663](https://github.com/sinatra/sinatra/pull/1663) by Yanis Zafirópulos
* Docs: English documentation: Document when `dump_errors` is enabled. Fixes [#1664](https://github.com/sinatra/sinatra/issues/1664) [#1665](https://github.com/sinatra/sinatra/pull/1665) by Patrik Ragnarsson
* Docs: Brazilian Portuguese documentation: Update README.pt-br.md with translation fixes. [#1668](https://github.com/sinatra/sinatra/pull/1668) by Vitor Oliveira
### CI
* Use latest JRuby 9.2.16.0 on CI. [#1682](https://github.com/sinatra/sinatra/pull/1682) by Olle Jonsson
* Switch CI from travis to GitHub Actions. [#1691](https://github.com/sinatra/sinatra/pull/1691) by namusyaka
* Skip the Slack action if `secrets.SLACK_WEBHOOK` is not set. [#1705](https://github.com/sinatra/sinatra/pull/1705) by Robin Wallin
* Small CI improvements. [#1703](https://github.com/sinatra/sinatra/pull/1703) by Robin Wallin
* Drop auto-generated boilerplate comments from CI configuration file. [#1728](https://github.com/sinatra/sinatra/pull/1728) by Olle Jonsson
### sinatra-contrib
* Do not raise when key is an enumerable. [#1619](https://github.com/sinatra/sinatra/pull/1619) by Ulysse Buonomo
### Rack protection
* Fix broken `origin_whitelist` option. Fixes [#1641](https://github.com/sinatra/sinatra/issues/1641) [#1642](https://github.com/sinatra/sinatra/pull/1642) by Takeshi YASHIRO
## 2.1.0 / 2020-09-05
* Fix additional Ruby 2.7 keyword warnings [#1586](https://github.com/sinatra/sinatra/pull/1586) by Stefan Sundin
* Drop Ruby 2.2 support [#1455](https://github.com/sinatra/sinatra/pull/1455) by Eloy Pérez
* Add Rack::Protection::ReferrerPolicy [#1291](https://github.com/sinatra/sinatra/pull/1291) by Stefan Sundin
* Add `default_content_type` setting. Fixes [#1238](https://github.com/sinatra/sinatra/pull/1238) [#1239](https://github.com/sinatra/sinatra/pull/1239) by Mike Pastore
* Allow `set :` in sinatra-namespace [#1255](https://github.com/sinatra/sinatra/pull/1255) by Christian Höppner
* Use prepend instead of include for helpers. Fixes [#1213](https://github.com/sinatra/sinatra/pull/1213) [#1214](https://github.com/sinatra/sinatra/pull/1214) by Mike Pastore
* Fix issue with passed routes and provides Fixes [#1095](https://github.com/sinatra/sinatra/pull/1095) [#1606](https://github.com/sinatra/sinatra/pull/1606) by Mike Pastore, Jordan Owens
* Add QuietLogger that excludes paths from Rack::CommonLogger [1250](https://github.com/sinatra/sinatra/pull/1250) by Christoph Wagner
* Sinatra::Contrib dependency updates. Fixes [#1207](https://github.com/sinatra/sinatra/pull/1207) [#1411](https://github.com/sinatra/sinatra/pull/1411) by Mike Pastore
* Allow CSP to fallback to default-src. Fixes [#1484](https://github.com/sinatra/sinatra/pull/1484) [#1490](https://github.com/sinatra/sinatra/pull/1490) by Jordan Owens
* Replace `origin_whitelist` with `permitted_origins`. Closes [#1620](https://github.com/sinatra/sinatra/issues/1620) [#1625](https://github.com/sinatra/sinatra/pull/1625) by rhymes
* Use Rainbows instead of thin for async/stream features. Closes [#1624](https://github.com/sinatra/sinatra/issues/1624) [#1627](https://github.com/sinatra/sinatra/pull/1627) by Ryuichi KAWAMATA
* Enable EscapedParams if passed via settings. Closes [#1615](https://github.com/sinatra/sinatra/issues/1615) [#1632](https://github.com/sinatra/sinatra/issues/1632) by Anders Bälter
* Support for parameters in mime types. Fixes [#1141](https://github.com/sinatra/sinatra/issues/1141) by John Hope
* Handle null byte when serving static files [#1574](https://github.com/sinatra/sinatra/issues/1574) by Kush Fanikiso
* Improve development support and documentation and source code by Olle Jonsson, Pierre-Adrien Buisson, Shota Iguchi
## 2.0.8.1 / 2020-01-02
* Allow multiple hashes to be passed in `merge` and `merge!` for `Sinatra::IndifferentHash` [#1572](https://github.com/sinatra/sinatra/pull/1572) by Shota Iguchi
## 2.0.8 / 2020-01-01
* Lookup Tilt class for template engine without loading files [#1558](https://github.com/sinatra/sinatra/pull/1558). Fixes [#1172](https://github.com/sinatra/sinatra/issues/1172) by Jordan Owens
* Add request info in NotFound exception [#1566](https://github.com/sinatra/sinatra/pull/1566) by Stefan Sundin
* Add `.yaml` support in `Sinatra::Contrib::ConfigFile` [#1564](https://github.com/sinatra/sinatra/issues/1564). Fixes [#1563](https://github.com/sinatra/sinatra/issues/1563) by Emerson Manabu Araki
* Remove only routing parameters from @params hash [#1569](https://github.com/sinatra/sinatra/pull/1569). Fixes [#1567](https://github.com/sinatra/sinatra/issues/1567) by Jordan Owens, Horacio
* Support `capture` and `content_for` with Hamlit [#1580](https://github.com/sinatra/sinatra/pull/1580) by Takashi Kokubun
* Eliminate warnings of keyword parameter for Ruby 2.7.0 [#1581](https://github.com/sinatra/sinatra/pull/1581) by Osamtimizer
## 2.0.7 / 2019-08-22
* Fix a regression [#1560](https://github.com/sinatra/sinatra/pull/1560) by Kunpei Sakai
## 2.0.6 / 2019-08-21
* Fix an issue setting environment from command line option [#1547](https://github.com/sinatra/sinatra/pull/1547), [#1554](https://github.com/sinatra/sinatra/pull/1554) by Jordan Owens, Kunpei Sakai
* Support pandoc as a new markdown renderer [#1533](https://github.com/sinatra/sinatra/pull/1533) by Vasiliy
* Remove outdated code for tilt 1.x [#1532](https://github.com/sinatra/sinatra/pull/1532) by Vasiliy
* Remove an extra logic for `force_encoding` [#1527](https://github.com/sinatra/sinatra/pull/1527) by Jordan Owens
* Avoid multiple errors even if `params` contains special values [#1526](https://github.com/sinatra/sinatra/pull/1527) by Kunpei Sakai
* Support `bundler/inline` with `require 'sinatra'` integration [#1520](https://github.com/sinatra/sinatra/pull/1520) by Kunpei Sakai
* Avoid `TypeError` when params contain a key without a value on Ruby < 2.4 [#1516](https://github.com/sinatra/sinatra/pull/1516) by Samuel Giddins
* Improve development support and documentation and source code by Olle Jonsson, Basavanagowda Kanur, Yuki MINAMIYA
## 2.0.5 / 2018-12-22
* Avoid FrozenError when params contains frozen value [#1506](https://github.com/sinatra/sinatra/pull/1506) by Kunpei Sakai
* Add support for Erubi [#1494](https://github.com/sinatra/sinatra/pull/1494) by @tkmru
* `IndifferentHash` monkeypatch warning improvements [#1477](https://github.com/sinatra/sinatra/pull/1477) by Mike Pastore
* Improve development support and documentation and source code by Anusree Prakash, Jordan Owens, @ceclinux and @krororo.
### sinatra-contrib
* Add `flush` option to `content_for` [#1225](https://github.com/sinatra/sinatra/pull/1225) by Shota Iguchi
* Drop activesupport dependency from sinatra-contrib [#1448](https://github.com/sinatra/sinatra/pull/1448)
* Update `yield_content` to append default to ERB template buffer [#1500](https://github.com/sinatra/sinatra/pull/1500) by Jordan Owens
### rack-protection
* Don't track the Accept-Language header by default [#1504](https://github.com/sinatra/sinatra/pull/1504) by Artem Chistyakov
## 2.0.4 / 2018-09-15
* Don't blow up when passing frozen string to `send_file` disposition [#1137](https://github.com/sinatra/sinatra/pull/1137) by Andrew Selder
* Fix ubygems LoadError [#1436](https://github.com/sinatra/sinatra/pull/1436) by Pavel Rosický
* Unescape regex captures [#1446](https://github.com/sinatra/sinatra/pull/1446) by Jordan Owens
* Slight performance improvements for IndifferentHash [#1427](https://github.com/sinatra/sinatra/pull/1427) by Mike Pastore
* Improve development support and documentation and source code by Will Yang, Jake Craige, Grey Baker and Guilherme Goettems Schneider
## 2.0.3 / 2018-06-09
* Fix the backports gem regression [#1442](https://github.com/sinatra/sinatra/issues/1442) by Marc-André Lafortune
## 2.0.2 / 2018-06-05
* Escape invalid query parameters [#1432](https://github.com/sinatra/sinatra/issues/1432) by Kunpei Sakai
* The patch fixes [CVE-2018-11627](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-11627).
* Fix undefined method error for `Sinatra::RequiredParams` with hash key [#1431](https://github.com/sinatra/sinatra/issues/1431) by Arpit Chauhan
* Add xml content-types to valid html_types for Rack::Protection [#1413](https://github.com/sinatra/sinatra/issues/1413) by Reenan Arbitrario
* Encode route parameters using :default_encoding setting [#1412](https://github.com/sinatra/sinatra/issues/1412) by Brian m. Carlson
* Fix unpredictable behaviour from Sinatra::ConfigFile [#1244](https://github.com/sinatra/sinatra/issues/1244) by John Hope
* Add Sinatra::IndifferentHash#slice [#1405](https://github.com/sinatra/sinatra/issues/1405) by Shota Iguchi
* Remove status code 205 from drop body response [#1398](https://github.com/sinatra/sinatra/issues/1398) by Shota Iguchi
* Ignore empty captures from params [#1390](https://github.com/sinatra/sinatra/issues/1390) by Shota Iguchi
* Improve development support and documentation and source code by Zp Yuan, Andreas Finger, Olle Jonsson, Shota Iguchi, Nikita Bulai and Joshua O'Brien
## 2.0.1 / 2018-02-17
* Repair nested namespaces, by avoiding prefix duplication [#1322](https://github.com/sinatra/sinatra/issues/1322). Fixes [#1310](https://github.com/sinatra/sinatra/issues/1310) by Kunpei Sakai
* Add pattern matches to values for Mustermann::Concat [#1333](https://github.com/sinatra/sinatra/issues/1333). Fixes [#1332](https://github.com/sinatra/sinatra/issues/1332) by Dawa Ometto
* Ship the VERSION file with the gem, to allow local unpacking [#1338](https://github.com/sinatra/sinatra/issues/1338) by Olle Jonsson
* Fix issue with custom error handler on bad request [#1351](https://github.com/sinatra/sinatra/issues/1351). Fixes [#1350](https://github.com/sinatra/sinatra/issues/1350) by Jordan Owens
* Override Rack::ShowExceptions#pretty to set custom template [#1377](https://github.com/sinatra/sinatra/issues/1377). Fixes [#1376](https://github.com/sinatra/sinatra/issues/1376) by Jordan Owens
* Enhanced path validation in Windows [#1379](https://github.com/sinatra/sinatra/issues/1379) by Orange Tsai from DEVCORE
* The patch fixes [CVE-2018-7212](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-7212)
* Improve development support and documentation by Faheel Ahmad, Shota Iguchi, Olle Jonsson, Manabu Niseki, John Hope, Horacio, Ice-Storm, GraniteRock, Raman Skaskevich, Carlos Azuaje, 284km, Dan Rice and Zachary Scott
## 2.0.0 / 2017-04-10
* Use Mustermann for patterns [#1086](https://github.com/sinatra/sinatra/issues/1086) by Konstantin Haase
* Server now provides `-q` flag for quiet mode, which disables start/stop messages [#1153](https://github.com/sinatra/sinatra/issues/1153) by Vasiliy.
* Session middleware can now be specified with `:session_store` setting [#1161](https://github.com/sinatra/sinatra/issues/1161) by Jordan Owens.
* `APP_ENV` is now preferred and recommended over `RACK_ENV` for setting environment [#984](https://github.com/sinatra/sinatra/issues/984) by Damien Mathieu.
* Add Reel support [#793](https://github.com/sinatra/sinatra/issues/793) by Patricio Mac Adden.
* Make route params available during error handling [#895](https://github.com/sinatra/sinatra/issues/895) by Jeremy Evans.
* Unify `not_found` and `error` 404 behavior [#896](https://github.com/sinatra/sinatra/issues/896) by Jeremy Evans.
* Enable Ruby 2.3 `frozen_string_literal` feature [#1076](https://github.com/sinatra/sinatra/issues/1076) by Vladimir Kochnev.
* Add Sinatra::ShowExceptions::TEMPLATE and patched Rack::ShowExceptions to prefer Sinatra template by Zachary Scott.
* Sinatra::Runner is used internally for integration tests [#840](https://github.com/sinatra/sinatra/issues/840) by Nick Sutterer.
* Fix case-sensitivity issue in `uri` method [#889](https://github.com/sinatra/sinatra/issues/889) by rennex.
* Use `Rack::Utils.status_code` to allow `status` helper to use symbol as well as numeric codes [#968](https://github.com/sinatra/sinatra/issues/968) by Tobias H. Michaelsen.
* Improved error handling for invalid params through Rack [#1070](https://github.com/sinatra/sinatra/issues/1070) by Jordan Owens.
* Ensure template is cached only once [#1021](https://github.com/sinatra/sinatra/issues/1021) by Patrik Rak.
* Rack middleware is initialized at server runtime rather than after receiving first request [#1205](https://github.com/sinatra/sinatra/issues/1205) by Itamar Turner-Trauring.
* Improve Session Secret documentation to encourage better security practices [#1218](https://github.com/sinatra/sinatra/issues/1218) by Glenn Rempe
* Exposed global and per-route options for Mustermann route parsing [#1233](https://github.com/sinatra/sinatra/issues/1233) by Mike Pastore
* Use same `session_secret` for classic and modular apps in development [#1245](https://github.com/sinatra/sinatra/issues/1245) by Marcus Stollsteimer
* Make authenticity token length a fixed value of 32 [#1181](https://github.com/sinatra/sinatra/issues/1181) by Jordan Owens
* Modernize Rack::Protection::ContentSecurityPolicy with CSP Level 2 and 3 Directives [#1202](https://github.com/sinatra/sinatra/issues/1202) by Glenn Rempe
* Adds preload option to Rack:Protection:StrictTransport [#1209](https://github.com/sinatra/sinatra/issues/1209) by Ed Robinson
* Improve BadRequest logic. Raise and handle exceptions if status is 400 [#1212](https://github.com/sinatra/sinatra/issues/1212) by Mike Pastore
* Make Rack::Test a development dependency [#1232](https://github.com/sinatra/sinatra/issues/1232) by Mike Pastore
* Capture exception messages of raised NotFound and BadRequest [#1210](https://github.com/sinatra/sinatra/issues/1210) by Mike Pastore
* Add explicit set method to contrib/cookies to override cookie settings [#1240](https://github.com/sinatra/sinatra/issues/1240) by Andrew Allen
* Avoid executing filters even if prefix matches with other namespace [#1253](https://github.com/sinatra/sinatra/issues/1253) by namusyaka
* Make `#has_key?` also indifferent in access, can accept String or Symbol [#1262](https://github.com/sinatra/sinatra/issues/1262) by Stephen Paul Weber
* Add `allow_if` option to bypass json csrf protection [#1265](https://github.com/sinatra/sinatra/issues/1265) by Jordan Owens
* rack-protection: Bundle StrictTransport, CookieTossing, and CSP [#1267](https://github.com/sinatra/sinatra/issues/1267) by Mike Pastore
* Add `:strict_paths` option for managing trailing slashes [#1273](https://github.com/sinatra/sinatra/issues/1273) by namusyaka
* Add full IndifferentHash implementation to params [#1279](https://github.com/sinatra/sinatra/issues/1279) by Mike Pastore
## 1.4.8 / 2017-01-30
* Fix the deprecation warning from Ruby about Fixnum. [#1235](https://github.com/sinatra/sinatra/issues/1235) by Akira Matsuda
## 1.4.7 / 2016-01-24
* Add Ashley Williams, Trevor Bramble, and Kashyap Kondamudi to team Sinatra.
* Correctly handle encoded colons in routes. (Jeremy Evans)
* Rename CHANGES to CHANGELOG.md and update Rakefile. [#1043](https://github.com/sinatra/sinatra/issues/1043) (Eliza Sorensen)
* Improve documentation. [#941](https://github.com/sinatra/sinatra/issues/941), [#1069](https://github.com/sinatra/sinatra/issues/1069), [#1075](https://github.com/sinatra/sinatra/issues/1075), [#1025](https://github.com/sinatra/sinatra/issues/1025), [#1052](https://github.com/sinatra/sinatra/issues/1052) (Many great folks)
* Introduce `Sinatra::Ext` to workaround Rack 1.6 bug to fix Ruby 1.8.7
support. [#1080](https://github.com/sinatra/sinatra/issues/1080) (Zachary Scott)
* Add CONTRIBUTING guide. [#987](https://github.com/sinatra/sinatra/issues/987) (Katrina Owen)
## 1.4.6 / 2015-03-23
* Improve tests and documentation. (Darío Hereñú, Seiichi Yonezawa, kyoendo,
John Voloski, Ferenc-, Renaud Martinet, Christian Haase, marocchino,
huoxito, Damir Svrtan, Amaury Medeiros, Jeremy Evans, Kashyap, shenqihui,
Ausmarton Fernandes, kami, Vipul A M, Lei Wu, 7stud, Taylor Shuler,
namusyaka, burningTyger, Cornelius Bock, detomastah, hakeda, John Hope,
Ruben Gonzalez, Andrey Deryabin, attilaolah, Anton Davydov, Nikita Penzin,
Dyego Costa)
* Remove duplicate require of sinatra/base. (Alexey Muranov)
* Escape HTML in 404 error page. (Andy Brody)
* Refactor to method call in `Stream#close` and `#callback`. (Damir Svrtan)
* Depend on latest version of Slim. (Damir Svrtan)
* Fix compatibility with Tilt version 2. (Yegor Timoschenko)
* Fix compatibility issue with Rack `pretty` method from ShowExceptions.
(Kashyap)
* Show date in local time in exception messages. (tayler1)
* Fix logo on error pages when using Ruby 1.8. (Jeremy Evans)
* Upgrade test suite to Minitest version 5 and fix Ruby 2.2 compatibility.
(Vipul A M)
## 1.4.5 / 2014-04-08
* Improve tests and documentation. (Seiichi Yonezawa, Mike Gehard, Andrew
Deitrick, Matthew Nicholas Bradley, GoGo tanaka, Carlos Lazo, Shim Tw,
kyoendo, Roman Kuznietsov, Stanislav Chistenko, Ryunosuke SATO, Ben Lewis,
wuleicanada, Patricio Mac Adden, Thais Camilo)
* Fix Ruby warnings. (Vipul A M, Piotr Szotkowski)
* Fix template cache memory leak. (Scott Holden)
* Work around UTF-8 bug in JRuby. (namusyaka)
* Don't set charset for JSON mime-type (Sebastian Borrazas)
* Fix bug in request.accept? that might trigger a NoMethodError. (sbonami)
## 1.4.4 / 2013-10-21
* Allow setting layout to false specifically for a single rendering engine.
(Matt Wildig)
* Allow using wildcard in argument passed to `request.accept?`. (wilkie)
* Treat missing Accept header like wild card. (Patricio Mac Adden)
* Improve tests and documentation. (Darío Javier Cravero, Armen P., michelc,
Patricio Mac Adden, Matt Wildig, Vipul A M, utenmiki, George Timoschenko,
Diogo Scudelletti)
* Fix Ruby warnings. (Vipul A M, Patricio Mac Adden)
* Improve self-hosted server started by `run!` method or in classic mode.
(Tobias Bühlmann)
* Reduce objects allocated per request. (Vipul A M)
* Drop unused, undocumented options hash from Sinatra.new. (George Timoschenko)
* Keep Content-Length header when response is a `Rack::File` or when streaming.
(Patricio Mac Adden, George Timoschenko)
* Use reel if it's the only server available besides webrick. (Tobias Bühlmann)
* Add `disable :traps` so setting up signal traps for self hosted server can be
skipped. (George Timoschenko)
* The `status` option passed to `send_file` may now be a string. (George
Timoschenko)
* Reduce file size of dev mode images for 404 and 500 pages. (Francis Go)
## 1.4.3 / 2013-06-07
* Running a Sinatra file directly or via `run!` it will now ignore an
empty $PORT env variable. (noxqsgit)
* Improve documentation. (burningTyger, Patricio Mac Adden,
Konstantin Haase, Diogo Scudelletti, Dominic Imhof)
* Expose matched pattern as env["sinatra.route"]. (Aman Gupta)
* Fix warning on Ruby 2.0. (Craig Little)
* Improve running subset of tests in isolation. (Viliam Pucik)
* Reorder private/public methods. (Patricio Mac Adden)
* Loosen version dependency for rack, so it runs with Rails 3.2.
(Konstantin Haase)
* Request#accept? now returns true instead of a truthy value. (Alan Harris)
## 1.4.2 / 2013-03-21
* Fix parsing error for case where both the pattern and the captured part
contain a dot. (Florian Hanke, Konstantin Haase)
* Missing Accept header is treated like */*. (Greg Denton)
* Improve documentation. (Patricio Mac Adden, Joe Bottigliero)
## 1.4.1 / 2013-03-15
* Make delegated methods available in config.ru (Konstantin Haase)
## 1.4.0 / 2013-03-15
* Add support for LINK and UNLINK requests. (Konstantin Haase)
* Add support for Yajl templates. (Jamie Hodge)
* Add support for Rabl templates. (Jesse Cooke)
* Add support for Wlang templates. (Bernard Lambeau)
* Add support for Stylus templates. (Juan David Pastas, Konstantin Haase)
* You can now pass a block to ERb, Haml, Slim, Liquid and Wlang templates,
which will be used when calling `yield` in the template. (Alexey Muranov)
* When running in classic mode, no longer include Sinatra::Delegator in Object,
instead extend the main object only. (Konstantin Haase)
* Improved route parsing: "/:name.?:format?" with "/foo.png" now matches to
{name: "foo", format: "png"} instead of {name: "foo.png"}. (Florian Hanke)
* Add :status option support to send_file. (Konstantin Haase)
* The `provides` condition now respects an earlier set content type.
(Konstantin Haase)
* Exception#code is only used when :use_code is enabled. Moreover, it will
be ignored if the value is not between 400 and 599. You should use
Exception#http_status instead. (Konstantin Haase)
* Status, headers and body will be set correctly in an after filter when using
halt in a before filter or route. (Konstantin Haase)
* Sinatra::Base.new now returns a Sinatra::Wrapper instance, exposing
`#settings` and `#helpers`, yet going through the middleware stack on
`#call`. It also implements a nice `#inspect`, so it plays nice with
Rails' `rake routes`. (Konstantin Haase)
* In addition to WebRick, Thin and Mongrel, Sinatra will now automatically pick
up Puma, Trinidad, ControlTower or Net::HTTP::Server when installed. The
logic for picking the server has been improved and now depends on the Ruby
implementation used. (Mark Rada, Konstantin Haase, Patricio Mac Adden)
* "Sinatra doesn't know this ditty" pages now show the app class when running
a modular application. This helps detecting where the response came from when
combining multiple modular apps. (Konstantin Haase)
* When port is not set explicitly, use $PORT env variable if set and only
default to 4567 if not. Plays nice with foreman. (Konstantin Haase)
* Allow setting layout on a per engine basis. (Zachary Scott, Konstantin Haase)
* You can now use `register` directly in a classic app. (Konstantin Haase)
* `redirect` now accepts URI or Addressable::URI instances. (Nicolas
Sanguinetti)
* Have Content-Disposition header also include file name for `inline`, not
just for `attachment`. (Konstantin Haase)
* Better compatibility to Rack 1.5. (James Tucker, Konstantin Haase)
* Make route parsing regex more robust. (Zoltan Dezso, Konstantin Haase)
* Improve Accept header parsing, expose parameters. (Pieter van de Bruggen,
Konstantin Haase)
* Add `layout_options` render option. Allows you, amongst other things, to
render a layout from a different folder. (Konstantin Haase)
* Explicitly setting `layout` to `nil` is treated like setting it to `false`.
(richo)
* Properly escape attributes in Content-Type header. (Pieter van de Bruggen)
* Default to only serving localhost in development mode. (Postmodern)
* Setting status code to 404 in error handler no longer triggers not_found
handler. (Konstantin Haase)
* The `protection` option now takes a `session` key for force
disabling/enabling session based protections. (Konstantin Haase)
* Add `x_cascade` option to disable `X-Cascade` header on missing route.
(Konstantin Haase)
* Improve documentation. (Kashyap, Stanislav Chistenko, Zachary Scott,
Anthony Accomazzo, Peter Suschlik, Rachel Mehl, ymmtmsys, Anurag Priyam,
burningTyger, Tony Miller, akicho8, Vasily Polovnyov, Markus Prinz,
Alexey Muranov, Erik Johnson, Vipul A M, Konstantin Haase)
* Convert documentation to Markdown. (Kashyap, Robin Dupret, burningTyger,
Vasily Polovnyov, Iain Barnett, Giuseppe Capizzi, Neil West)
* Don't set not_found content type to HTML in development mode with custom
not_found handler. (Konstantin Haase)
* Fix mixed indentation for private methods. (Robin Dupret)
* Recalculate Content-Length even if hard coded if body is reset. Relevant
mostly for error handlers. (Nathan Esquenazi, Konstantin Haase)
* Plus sign is once again kept as such when used for URL matches. (Konstantin
Haase)
* Take views option into account for template caching. (Konstantin Haase)
* Consistent use of `headers` instead of `header` internally. (Patricio Mac Adden)
* Fix compatibility to RDoc 4. (Bohuslav Kabrda)
* Make chat example work with latest jQuery. (loveky, Tony Miller)
* Make tests run without warnings. (Patricio Mac Adden)
* Make sure value returned by `mime_type` is a String or nil, even when a
different object is passed in, like an AcceptEntry. (Konstantin Haase)
* Exceptions in `after` filter are now handled like any other exception.
(Nathan Esquenazi)
## 1.3.6 (backport release) / 2013-03-15
Backported from 1.4.0:
* Take views option into account for template caching. (Konstantin Haase)
* Improve documentation (Konstantin Haase)
* No longer override `define_singleton_method`. (Konstantin Haase)
## 1.3.5 / 2013-02-25
* Fix for RubyGems 2.0 (Uchio KONDO)
* Improve documentation (Konstantin Haase)
* No longer override `define_singleton_method`. (Konstantin Haase)
## 1.3.4 / 2013-01-26
* Improve documentation. (Kashyap, Stanislav Chistenko, Konstantin Haase,
ymmtmsys, Anurag Priyam)
* Adjustments to template system to work with Tilt edge. (Konstantin Haase)
* Fix streaming with latest Rack release. (Konstantin Haase)
* Fix default content type for Sinatra::Response with latest Rack release.
(Konstantin Haase)
* Fix regression where + was no longer treated like space. (Ross Boucher)
* Status, headers and body will be set correctly in an after filter when using
halt in a before filter or route. (Konstantin Haase)
## 1.3.3 / 2012-08-19
* Improved documentation. (burningTyger, Konstantin Haase, Gabriel Andretta,
Anurag Priyam, michelc)
* No longer modify the load path. (Konstantin Haase)
* When keeping a stream open, set up callback/errback correctly to deal with
clients closing the connection. (Konstantin Haase)
* Fix bug where having a query param and a URL param by the same name would
concatenate the two values. (Konstantin Haase)
* Prevent duplicated log output when application is already wrapped in a
`Rack::CommonLogger`. (Konstantin Haase)
* Fix issue where `Rack::Link` and Rails were preventing indefinite streaming.
(Konstantin Haase)
* No longer cause warnings when running Ruby with `-w`. (Konstantin Haase)
* HEAD requests on static files no longer report a Content-Length of 0, but
instead the proper length. (Konstantin Haase)
* When protecting against CSRF attacks, drop the session instead of refusing
the request. (Konstantin Haase)
## 1.3.2 / 2011-12-30
* Don't automatically add `Rack::CommonLogger` if `Rack::Server` is adding it,
too. (Konstantin Haase)
* Setting `logging` to `nil` will avoid setting up `Rack::NullLogger`.
(Konstantin Haase)
* Route specific params are now available in the block passed to #stream.
(Konstantin Haase)
* Fix bug where rendering a second template in the same request, after the
first one raised an exception, skipped the default layout. (Nathan Baum)
* Fix bug where parameter escaping got enabled when disabling a different
protection. (Konstantin Haase)
* Fix regression: Filters without a pattern may now again manipulate the params
hash. (Konstantin Haase)
* Added examples directory. (Konstantin Haase)
* Improved documentation. (Gabriel Andretta, Markus Prinz, Erick Zetta, Just
Lest, Adam Vaughan, Aleksander Dąbrowski)
* Improved MagLev support. (Tim Felgentreff)
## 1.3.1 / 2011-10-05
* Support adding more than one callback to the stream object. (Konstantin
Haase)
* Fix for infinite loop when streaming on 1.9.2 with Thin from a modular
application (Konstantin Haase)
## 1.3.0 / 2011-09-30
* Added `stream` helper method for easily creating streaming APIs, Server
Sent Events or even WebSockets. See README for more on that topic.
(Konstantin Haase)
* If a HTTP 1.1 client is redirected from a different verb than GET, use 303
instead of 302 by default. You may still pass 302 explicitly. Fixes AJAX
redirects in Internet Explorer 9 (to be fair, everyone else is doing it
wrong and IE is behaving correct). (Konstantin Haase)
* Added support for HTTP PATCH requests. (Konstantin Haase)
* Use rack-protection to defend against common opportunistic attacks.
(Josh Lane, Jacob Burkhart, Konstantin Haase)
* Support for Creole templates, Creole is a standardized wiki markup,
supported by many wiki implementations. (Konstanin Haase)
* The `erubis` method has been deprecated. If Erubis is available, Sinatra
will automatically use it for rendering ERB templates. `require 'erb'`
explicitly to prevent that behavior. (Magnus Holm, Ryan Tomayko, Konstantin
Haase)
* Patterns now match against the escaped URLs rather than the unescaped
version. This makes Sinatra confirm with RFC 2396 section 2.2 and RFC 2616
section 3.2.3 (escaped reserved characters should not be treated like the
unescaped version), meaning that "/:name" will also match `/foo%2Fbar`, but
not `/foo/bar`. To avoid incompatibility, pattern matching has been
adjusted. Moreover, since we do no longer need to keep an unescaped version
of path_info around, we handle all changes to `env['PATH_INFO']` correctly.
(Konstantin Haase)
* `settings.app_file` now defaults to the file subclassing `Sinatra::Base` in
modular applications. (Konstantin Haase)
* Set up `Rack::Logger` or `Rack::NullLogger` depending on whether logging
was enabled or not. Also, expose that logger with the `logger` helper
method. (Konstantin Haase)
* The sessions setting may be an options hash now. (Konstantin Haase)
* Important: Ruby 1.8.6 support has been dropped. This version also depends
on at least Rack 1.3.0. This means that it is incompatible with Rails prior
to 3.1.0. Please use 1.2.x if you require an earlier version of Ruby or
Rack, which we will continue to supply with bug fixes. (Konstantin Haase)
* Renamed `:public` to `:public_folder` to avoid overriding Ruby's built-in
`public` method/keyword. `set(:public, ...)` is still possible but shows a
warning. (Konstantin Haase)
* It is now possible to use a different target class for the top level DSL
(aka classic style) than `Sinatra::Application` by setting
`Delegator.target`. This was mainly introduced to ease testing. (Konstantin
Haase)
* Error handlers defined for an error class will now also handle subclasses
of that class, unless more specific error handlers exist. (Konstantin
Haase)
* Error handling respects Exception#code, again. (Konstantin Haase)
* Changing a setting will merge hashes: `set(:x, :a => 1); set(:x :b => 2)`
will result in `{:a => 1, :b => 2}`. Use `set(:x, {:a => 1}, true)` to
avoid this behavior. (Konstantin Haase)
* Added `request.accept?` and `request.preferred_type` to ease dealing with
`Accept` headers. (Konstantin Haase)
* Added `:static_cache_control` setting to automatically set cache control
headers to static files. (Kenichi Nakamura)
* Added `informal?`, `success?`, `redirect?`, `client_error?`,
`server_error?` and `not_found?` helper methods to ease dealing with status
codes. (Konstantin Haase)
* Uses SecureRandom to generate default session secret. (Konstantin Haase)
* The `attachment` helper will set Content-Type (if it hasn't been set yet)
depending on the supplied file name. (Vasiliy Ermolovich)
* Conditional requests on `etag` helper now work properly for unsafe HTTP
methods. (Matthew Schinckel, Konstantin Haase)
* The `last_modified` helper does not stop execution and change the status code
if the status code is something different than 200. (Konstantin Haase)
* Added support for If-Unmodified-Since header. (Konstantin Haase)
* `Sinatra::Base.run!` now prints to stderr rather than stdout. (Andrew
Armenia)
* `Sinatra::Base.run!` takes a block allowing access to the Rack handler.
(David Waite)
* Automatic `app_file` detection now works in directories containing brackets
(Konstantin Haase)
* Exception objects are now passed to error handlers. (Konstantin Haase)
* Improved documentation. (Emanuele Vicentini, Peter Higgins, Takanori
Ishikawa, Konstantin Haase)
* Also specify charset in Content-Type header for JSON. (Konstantin Haase)
* Rack handler names will not be converted to lower case internally, this
allows you to run Sinatra with custom Rack handlers, like Kirk or Mongrel2.
Example: `ruby app.rb -s Mongrel2` (Konstantin Haase)
* Ignore `to_ary` on response bodies. Fixes compatibility to Rails 3.1.
(Konstantin Haase)
* Middleware setup is now distributed across multiple methods, allowing
Sinatra extensions to easily hook into the setup process. (Konstantin
Haase)
* Internal refactoring and minor performance improvements. (Konstantin Haase)
* Move Sinatra::VERSION to separate file, so it can be checked without
loading Sinatra. (Konstantin Haase)
* Command line options now complain if value passed to `-p` is not a valid
integer. (Konstantin Haase)
* Fix handling of broken query params when displaying exceptions. (Luke
Jahnke)
## 1.2.9 (backports release) / 2013-03-15
IMPORTANT: THIS IS THE LAST 1.2.x RELEASE, PLEASE UPGRADE.
* Display EOL warning when loading Sinatra. (Konstantin Haase)
* Improve documentation. (Anurag Priyam, Konstantin Haase)
* Do not modify the load path. (Konstantin Haase)
* Display deprecation warning if RUBY_IGNORE_CALLERS is used. (Konstantin Haase)
* Add backports library so we can still run on Ruby 1.8.6. (Konstantin Haase)
## 1.2.8 (backports release) / 2011-12-30
Backported from 1.3.2:
* Fix bug where rendering a second template in the same request after the
first one raised an exception skipped the default layout (Nathan Baum)
## 1.2.7 (backports release) / 2011-09-30
Custom changes:
* Fix Ruby 1.8.6 issue with Accept header parsing. (Konstantin Haase)
Backported from 1.3.0:
* Ignore `to_ary` on response bodies. Fixes compatibility to Rails 3.1.
(Konstantin Haase)
* `Sinatra.run!` now prints to stderr rather than stdout. (Andrew Armenia)
* Automatic `app_file` detection now works in directories containing brackets
(Konstantin Haase)
* Improved documentation. (Emanuele Vicentini, Peter Higgins, Takanori
Ishikawa, Konstantin Haase)
* Also specify charset in Content-Type header for JSON. (Konstantin Haase)
* Rack handler names will not be converted to lower case internally, this
allows you to run Sinatra with custom Rack handlers, like Kirk or Mongrel2.
Example: `ruby app.rb -s Mongrel2` (Konstantin Haase)
* Fix uninitialized instance variable warning. (David Kellum)
* Command line options now complain if value passed to `-p` is not a valid
integer. (Konstantin Haase)
* Fix handling of broken query params when displaying exceptions. (Luke
Jahnke)
## 1.2.6 / 2011-05-01
* Fix broken delegation, backport delegation tests from Sinatra 1.3.
(Konstantin Haase)
## 1.2.5 / 2011-04-30
* Restore compatibility with Ruby 1.8.6. (Konstantin Haase)
## 1.2.4 / 2011-04-30
* Sinatra::Application (classic style) does not use a session secret in
development mode, so sessions are not invalidated after every request when
using Shotgun. (Konstantin Haase)
* The request object was shared between multiple Sinatra instances in the
same middleware chain. This caused issues if any non-sinatra routing
happened in-between two of those instances, or running a request twice
against an application (described in the README). The caching was reverted.
See GH[#239](https://github.com/sinatra/sinatra/issues/239) and GH[#256](https://github.com/sinatra/sinatra/issues/256) for more infos. (Konstantin Haase)
* Fixes issues where the top level DSL was interfering with method_missing
proxies. This issue surfaced when Rails 3 was used with older Sass versions
and Sinatra >= 1.2.0. (Konstantin Haase)
* Sinatra::Delegator.delegate is now able to delegate any method names, even
those containing special characters. This allows better integration into
other programming languages on Rubinius (probably on the JVM, too), like
Fancy. (Konstantin Haase)
* Remove HEAD request logic and let Rack::Head handle it instead. (Paolo
"Nusco" Perrotta)
## 1.2.3 / 2011-04-13
* This release is compatible with Tilt 1.3, it will still work with Tilt 1.2.2,
however, if you want to use a newer Tilt version, you have to upgrade to at
least this version of Sinatra. (Konstantin Haase)
* Helpers dealing with time, like `expires`, handle objects that pretend to be
numbers, like `ActiveSupport::Duration`, better. (Konstantin Haase)
## 1.2.2 / 2011-04-08
* The `:provides => :js` condition now matches both `application/javascript`
and `text/javascript`. The `:provides => :xml` condition now matches both
`application/xml` and `text/xml`. The `Content-Type` header is set
accordingly. If the client accepts both, the `application/*` version is
preferred, since the `text/*` versions are deprecated. (Konstantin Haase)
* The `provides` condition now handles wildcards in `Accept` headers correctly.
Thus `:provides => :html` matches `text/html`, `text/*` and `*/*`.
(Konstantin Haase)
* When parsing `Accept` headers, `Content-Type` preferences are honored
according to RFC 2616 section 14.1. (Konstantin Haase)
* URIs passed to the `url` helper or `redirect` may now use any schema to be
identified as absolute URIs, not only `http` or `https`. (Konstantin Haase)
* Handles `Content-Type` strings that already contain parameters correctly in
`content_type` (example: `content_type "text/plain; charset=utf-16"`).
(Konstantin Haase)
* If a route with an empty pattern is defined (`get("") { ... }`) requests with
an empty path info match this route instead of "/". (Konstantin Haase)
* In development environment, when running under a nested path, the image URIs
on the error pages are set properly. (Konstantin Haase)
## 1.2.1 / 2011-03-17
* Use a generated session secret when using `enable :sessions`. (Konstantin
Haase)
* Fixed a bug where the wrong content type was used if no content type was set
and a template engine was used with a different engine for the layout with
different default content types, say Less embedded in Slim. (Konstantin
Haase)
* README translations improved (Gabriel Andretta, burningTyger, Sylvain Desvé,
Gregor Schmidt)
## 1.2.0 / 2011-03-03
* Added `slim` rendering method for rendering Slim templates. (Steve
Hodgkiss)
* The `markaby` rendering method now allows passing a block, making inline
usage possible. Requires Tilt 1.2 or newer. (Konstantin Haase)
* All render methods now take a `:layout_engine` option, allowing to use a
layout in a different template language. Even more useful than using this
directly (`erb :index, :layout_engine => :haml`) is setting this globally for
a template engine that otherwise does not support layouts, like Markdown or
Textile (`set :markdown, :layout_engine => :erb`). (Konstantin Haase)
* Before and after filters now support conditions, both with and without
patterns (`before '/api/*', :agent => /Songbird/`). (Konstantin Haase)
* Added a `url` helper method which constructs absolute URLs. Copes with
reverse proxies and Rack handlers correctly. Aliased to `to`, so you can
write `redirect to('/foo')`. (Konstantin Haase)
* If running on 1.9, patterns for routes and filters now support named
captures: `get(%r{/hi/(?[^/?#]+)}) { "Hi #{params['name']}" }`.
(Steve Price)
* All rendering methods now take a `:scope` option, which renders them in
another context. Note that helpers and instance variables will be
unavailable if you use this feature. (Paul Walker)
* The behavior of `redirect` can now be configured with `absolute_redirects`
and `prefixed_redirects`. (Konstantin Haase)
* `send_file` now allows overriding the Last-Modified header, which defaults
to the file's mtime, by passing a `:last_modified` option. (Konstantin Haase)
* You can use your own template lookup method by defining `find_template`.
This allows, among other things, using more than one views folder.
(Konstantin Haase)
* Largely improved documentation. (burningTyger, Vasily Polovnyov, Gabriel
Andretta, Konstantin Haase)
* Improved error handling. (cactus, Konstantin Haase)
* Skip missing template engines in tests correctly. (cactus)
* Sinatra now ships with a Gemfile for development dependencies, since it eases
supporting different platforms, like JRuby. (Konstantin Haase)
## 1.1.4 (backports release) / 2011-04-13
* Compatible with Tilt 1.3. (Konstantin Haase)
## 1.1.3 / 2011-02-20
* Fixed issues with `user_agent` condition if the user agent header is missing.
(Konstantin Haase)
* Fix some routing tests that have been skipped by accident (Ross A. Baker)
* Fix rendering issues with Builder and Nokogiri (Konstantin Haase)
* Replace last_modified helper with better implementation. (cactus,
Konstantin Haase)
* Fix issue with charset not being set when using `provides` condition.
(Konstantin Haase)
* Fix issue with `render` not picking up all alternative file extensions for
a rendering engine - it was not possible to register ".html.erb" without
tricks. (Konstantin Haase)
## 1.1.2 / 2010-10-25
Like 1.1.1, but with proper CHANGES file.
## 1.1.1 / 2010-10-25
* README has been translated to Russian (Nickolay Schwarz, Vasily Polovnyov)
and Portuguese (Luciano Sousa).
* Nested templates without a `:layout` option can now be used from the layout
template without causing an infinite loop. (Konstantin Haase)
* Inline templates are now encoding aware and can therefore be used with
unicode characters on Ruby 1.9. Magic comments at the beginning of the file
will be honored. (Konstantin Haase)
* Default `app_file` is set correctly when running with bundler. Using
bundler caused Sinatra not to find the `app_file` and therefore not to find
the `views` folder on it's own. (Konstantin Haase)
* Better handling of Content-Type when using `send_file`: If file extension
is unknown, fall back to `application/octet-stream` and do not override
content type if it has already been set, except if `:type` is passed
explicitly (Konstantin Haase)
* Path is no longer cached if changed between handlers that do pattern
matching. This means you can change `request.path_info` in a pattern
matching before filter. (Konstantin Haase)
* Headers set by cache_control now always set max_age as an Integer, making
sure it is compatible with RFC2616. (Konstantin Haase)
* Further improved handling of string encodings on Ruby 1.9, templates now
honor default_encoding and URLs support unicode characters. (Konstantin
Haase)
## 1.1.0 / 2010-10-24
* Before and after filters now support pattern matching, including the
ability to use captures: "before('/user/:name') { |name| ... }". This
avoids manual path checking. No performance loss if patterns are avoided.
(Konstantin Haase)
* It is now possible to render SCSS files with the `scss` method, which
behaves exactly like `sass` except for the different file extension and
assuming the SCSS syntax. (Pedro Menezes, Konstantin Haase)
* Added `liquid`, `markdown`, `nokogiri`, `textile`, `rdoc`, `radius`,
`markaby`, and `coffee` rendering methods for rendering Liquid, Markdown,
Nokogiri, Textile, RDoc, Radius, Markaby and CoffeeScript templates.
(Konstantin Haase)
* Now supports byte-range requests (the HTTP_RANGE header) for static files.
Multi-range requests are not supported, however. (Jens Alfke)
* You can now use #settings method from class and top level for convenience.
(Konstantin Haase)
* Setting multiple values now no longer relies on #to_hash and therefore
accepts any Enumerable as parameter. (Simon Rozet)
* Nested templates default the `layout` option to `false` rather than `true`.
This eases the use of partials. If you wanted to render one haml template
embedded in another, you had to call `haml :partial, {}, :layout => false`.
As you almost never want the partial to be wrapped in the standard layout
in this situation, you now only have to call `haml :partial`. Passing in
`layout` explicitly is still possible. (Konstantin Haase)
* If a the return value of one of the render functions is used as a response
body and the content type has not been set explicitly, Sinatra chooses a
content type corresponding to the rendering engine rather than just using
"text/html". (Konstantin Haase)
* README is now available in Chinese (Wu Jiang), French (Mickael Riga),
German (Bernhard Essl, Konstantin Haase, burningTyger), Hungarian (Janos
Hardi) and Spanish (Gabriel Andretta). The extremely outdated Japanese
README has been updated (Kouhei Yanagita).
* It is now possible to access Sinatra's template_cache from the outside.
(Nick Sutterer)
* The `last_modified` method now also accepts DateTime instances and makes
sure the header will always be set to a string. (Konstantin Haase)
* 599 now is a legal status code. (Steve Shreeve)
* This release is compatible with Ruby 1.9.2. Sinatra was trying to read
non existent files Ruby added to the call stack. (Shota Fukumori,
Konstantin Haase)
* Prevents a memory leak on 1.8.6 in production mode. Note, however, that
this is due to a bug in 1.8.6 and request will have the additional overhead
of parsing templates again on that version. It is recommended to use at
least Ruby 1.8.7. (Konstantin Haase)
* Compares last modified date correctly. `last_modified` was halting only
when the 'If-Modified-Since' header date was equal to the time specified.
Now, it halts when is equal or later than the time specified (Gabriel
Andretta).
* Sinatra is now usable in combination with Rails 3. When mounting a Sinatra
application under a subpath in Rails 3, the PATH_INFO is not prefixed with
a slash and no routes did match. (José Valim)
* Better handling of encodings in 1.9, defaults params encoding to UTF-8.
(Konstantin Haase)
* `show_exceptions` handling is now triggered after custom error handlers, if
it is set to `:after_handlers`, thus not disabling those handler in
development mode. (pangel, Konstantin Haase)
* Added ability to handle weighted HTTP_ACCEPT headers. (Davide D'Agostino)
* `send_file` now always respects the `:type` option if set. Previously it
was discarded if no matching mime type was found, which made it impossible
to directly pass a mime type. (Konstantin Haase)
* `redirect` always redirects to an absolute URI, even if a relative URI was
passed. Ensures compatibility with RFC 2616 section 14.30. (Jean-Philippe
Garcia Ballester, Anthony Williams)
* Broken examples for using Erubis, Haml and Test::Unit in README have been
fixed. (Nick Sutterer, Doug Ireton, Jason Stewart, Eric Marden)
* Sinatra now handles SIGTERM correctly. (Patrick Collison)
* Fixes an issue with inline templates in modular applications that manually
call `run!`. (Konstantin Haase)
* Spaces after inline template names are now ignored (Konstantin Haase)
* It's now possible to use Sinatra with different package management
systems defining a custom require. (Konstantin Haase)
* Lighthouse has been dropped in favor of GitHub issues.
* Tilt is now a dependency and therefore no longer ships bundled with
Sinatra. (Ryan Tomayko, Konstantin Haase)
* Sinatra now depends on Rack 1.1 or higher. Rack 1.0 is no longer supported.
(Konstantin Haase)
## 1.0 / 2010-03-23
* It's now possible to register blocks to run after each request using
after filters. After filters run at the end of each request, after
routes and error handlers. (Jimmy Schementi)
* Sinatra now uses Tilt for rendering
templates. This adds support for template caching, consistent
template backtraces, and support for new template engines, like
mustache and liquid. (Ryan Tomayko)
* ERB, Erubis, and Haml templates are now compiled the first time
they're rendered instead of being string eval'd on each invocation.
Benchmarks show a 5x-10x improvement in render time. This also
reduces the number of objects created, decreasing pressure on Ruby's
GC. (Ryan Tomayko)
* New 'settings' method gives access to options in both class and request
scopes. This replaces the 'options' method. (Chris Wanstrath)
* New boolean 'reload_templates' setting controls whether template files
are reread from disk and recompiled on each request. Template read/compile
is cached by default in all environments except development. (Ryan Tomayko)
* New 'erubis' helper method for rendering ERB template with Erubis. The
erubis gem is required. (Dylan Egan)
* New 'cache_control' helper method provides a convenient way of
setting the Cache-Control response header. Takes a variable number
of boolean directives followed by a hash of value directives, like
this: cache_control :public, :must_revalidate, :max_age => 60
(Ryan Tomayko)
* New 'expires' helper method is like cache_control but takes an
integer number of seconds or Time object:
expires 300, :public, :must_revalidate
(Ryan Tomayko)
* New request.secure? method for checking for an SSL connection.
(Adam Wiggins)
* Sinatra apps can now be run with a `-o ` argument to specify
the address to bind to. (Ryan Tomayko)
* Rack::Session::Cookie is now added to the middleware pipeline when
running in test environments if the :sessions option is set.
(Simon Rozet)
* Route handlers, before filters, templates, error mappings, and
middleware are now resolved dynamically up the inheritance hierarchy
when needed instead of duplicating the superclass's version when
a new Sinatra::Base subclass is created. This should fix a variety
of issues with extensions that need to add any of these things
to the base class. (Ryan Tomayko)
* Exception error handlers always override the raise_errors option now.
Previously, all exceptions would be raised outside of the application
when the raise_errors option was enabled, even if an error handler was
defined for that exception. The raise_errors option now controls
whether unhandled exceptions are raised (enabled) or if a generic 500
error is returned (disabled). (Ryan Tomayko)
* The X-Cascade response header is set to 'pass' when no matching route
is found or all routes pass. (Josh Peek)
* Filters do not run when serving static files anymore. (Ryan Tomayko)
* pass takes an optional block to be used as the route handler if no
subsequent route matches the request. (Blake Mizerany)
The following Sinatra features have been obsoleted (removed entirely) in
the 1.0 release:
* The `sinatra/test` library is obsolete. This includes the `Sinatra::Test`
module, the `Sinatra::TestHarness` class, and the `get_it`, `post_it`,
`put_it`, `delete_it`, and `head_it` helper methods. The
[`Rack::Test` library](http://gitrdoc.com/brynary/rack-test) should
be used instead.
* Test framework specific libraries (`sinatra/test/spec`,
`sinatra/test/bacon`,`sinatra/test/rspec`, etc.) are obsolete. See
http://www.sinatrarb.com/testing.html for instructions on setting up a
testing environment under each of these frameworks.
* `Sinatra::Default` is obsolete; use `Sinatra::Base` instead.
`Sinatra::Base` acts more like `Sinatra::Default` in development mode.
For example, static file serving and sexy development error pages are
enabled by default.
* Auto-requiring template libraries in the `erb`, `builder`, `haml`,
and `sass` methods is obsolete due to thread-safety issues. You must
require the template libraries explicitly in your app.
* The `:views_directory` option to rendering methods is obsolete; use
`:views` instead.
* The `:haml` and `:sass` options to rendering methods are obsolete.
Template engine options should be passed in the second Hash argument
instead.
* The `use_in_file_templates` method is obsolete. Use
`enable :inline_templates` or `set :inline_templates, 'path/to/file'`
* The 'media_type' helper method is obsolete. Use 'mime_type' instead.
* The 'mime' main and class method is obsolete. Use 'mime_type' instead.
* The request-level `send_data` method is no longer supported.
* The `Sinatra::Event` and `Sinatra::EventContext` classes are no longer
supported. This may effect extensions written for versions prior to 0.9.2.
See [Writing Sinatra Extensions](http://www.sinatrarb.com/extensions.html)
for the officially supported extensions API.
* The `set_option` and `set_options` methods are obsolete; use `set`
instead.
* The `:env` setting (`settings.env`) is obsolete; use `:environment`
instead.
* The request level `stop` method is obsolete; use `halt` instead.
* The request level `entity_tag` method is obsolete; use `etag`
instead.
* The request level `headers` method (HTTP response headers) is obsolete;
use `response['Header-Name']` instead.
* `Sinatra.application` is obsolete; use `Sinatra::Application` instead.
* Using `Sinatra.application = nil` to reset an application is obsolete.
This should no longer be necessary.
* Using `Sinatra.default_options` to set base configuration items is
obsolete; use `Sinatra::Base.set(key, value)` instead.
* The `Sinatra::ServerError` exception is obsolete. All exceptions raised
within a request are now treated as internal server errors and result in
a 500 response status.
* The `:methodoverride' option to enable/disable the POST _method hack is
obsolete; use `:method_override` instead.
## 0.9.2 / 2009-05-18
* This version is compatible with Rack 1.0. [Rein Henrichs]
* The development-mode unhandled exception / error page has been
greatly enhanced, functionally and aesthetically. The error
page is used when the :show_exceptions option is enabled and an
exception propagates outside of a route handler or before filter.
[Simon Rozet / Matte Noble / Ryan Tomayko]
* Backtraces that move through templates now include filenames and
line numbers where possible. [#51 / S. Brent Faulkner]
* All templates now have an app-level option for setting default
template options (:haml, :sass, :erb, :builder). The app-level
option value must be a Hash if set and is merged with the
template options specified to the render method (Base#haml,
Base#erb, Base#builder). [S. Brent Faulkner, Ryan Tomayko]
* The method signature for all template rendering methods has
been unified: "def engine(template, options={}, locals={})".
The options Hash now takes the generic :views, :layout, and
:locals options but also any template-specific options. The
generic options are removed before calling the template specific
render method. Locals may be specified using either the
:locals key in the options hash or a second Hash option to the
rendering method. [#191 / Ryan Tomayko]
* The receiver is now passed to "configure" blocks. This
allows for the following idiom in top-level apps:
configure { |app| set :foo, app.root + '/foo' }
[TJ Holowaychuck / Ryan Tomayko]
* The "sinatra/test" lib is deprecated and will be removed in
Sinatra 1.0. This includes the Sinatra::Test module and
Sinatra::TestHarness class in addition to all the framework
test helpers that were deprecated in 0.9.1. The Rack::Test
lib should be used instead: http://gitrdoc.com/brynary/rack-test
[#176 / Simon Rozet]
* Development mode source file reloading has been removed. The
"shotgun" (http://rtomayko.github.com/shotgun/) program can be
used to achieve the same basic functionality in most situations.
Passenger users should use the "tmp/always_restart.txt"
file (http://tinyurl.com/c67o4h). [#166 / Ryan Tomayko]
* Auto-requiring template libs in the erb, builder, haml, and
sass methods is deprecated due to thread-safety issues. You must
require the template libs explicitly in your app file. [Simon Rozet]
* A new Sinatra::Base#route_missing method was added. route_missing
is sent when no route matches the request or all route handlers
pass. The default implementation forwards the request to the
downstream app when running as middleware (i.e., "@app" is
non-nil), or raises a NotFound exception when no downstream app
is defined. Subclasses can override this method to perform custom
route miss logic. [Jon Crosby]
* A new Sinatra::Base#route_eval method was added. The method
yields to the block and throws :halt with the result. Subclasses
can override this method to tap into the route execution logic.
[TJ Holowaychuck]
* Fix the "-x" (enable request mutex / locking) command line
argument. Passing -x now properly sets the :lock option.
[S. Brent Faulkner, Ryan Tomayko]
* Fix writer ("foo=") and predicate ("foo?") methods in extension
modules not being added to the registering class.
[#172 / Pat Nakajima]
* Fix in-file templates when running alongside activesupport and
fatal errors when requiring activesupport before sinatra
[#178 / Brian Candler]
* Fix various issues running on Google AppEngine.
[Samuel Goebert, Simon Rozet]
* Fix in-file templates __END__ detection when __END__ exists with
other stuff on a line [Yoji Shidara]
## 0.9.1.1 / 2009-03-09
* Fix directory traversal vulnerability in default static files
route. See [#177] for more info.
## 0.9.1 / 2009-03-01
* Sinatra now runs under Ruby 1.9.1 [#61]
* Route patterns (splats, :named, or Regexp captures) are now
passed as arguments to the block. [#140]
* The "helpers" method now takes a variable number of modules
along with the normal block syntax. [#133]
* New request-level #forward method for middleware components: passes
the env to the downstream app and merges the response status, headers,
and body into the current context. [#126]
* Requests are now automatically forwarded to the downstream app when
running as middleware and no matching route is found or all routes
pass.
* New simple API for extensions/plugins to add DSL-level and
request-level methods. Use Sinatra.register(mixin) to extend
the DSL with all public methods defined in the mixin module;
use Sinatra.helpers(mixin) to make all public methods defined
in the mixin module available at the request level. [#138]
See http://www.sinatrarb.com/extensions.html for details.
* Named parameters in routes now capture the "." character. This makes
routes like "/:path/:filename" match against requests like
"/foo/bar.txt"; in this case, "params[:filename]" is "bar.txt".
Previously, the route would not match at all.
* Added request-level "redirect back" to redirect to the referring
URL.
* Added a new "clean_trace" option that causes backtraces dumped
to rack.errors and displayed on the development error page to
omit framework and core library backtrace lines. The option is
enabled by default. [#77]
* The ERB output buffer is now available to helpers via the @_out_buf
instance variable.
* It's now much easier to test sessions in unit tests by passing a
":session" option to any of the mock request methods. e.g.,
get '/', {}, :session => { 'foo' => 'bar' }
* The testing framework specific files ('sinatra/test/spec',
'sinatra/test/bacon', 'sinatra/test/rspec', etc.) have been deprecated.
See http://sinatrarb.com/testing.html for instructions on setting up
a testing environment with these frameworks.
* The request-level #send_data method from Sinatra 0.3.3 has been added
for compatibility but is deprecated.
* Fix :provides causing crash on any request when request has no
Accept header [#139]
* Fix that ERB templates were evaluated twice per "erb" call.
* Fix app-level middleware not being run when the Sinatra application is
run as middleware.
* Fixed some issues with running under Rack's CGI handler caused by
writing informational stuff to stdout.
* Fixed that reloading was sometimes enabled when starting from a
rackup file [#110]
* Fixed that "." in route patterns erroneously matched any character
instead of a literal ".". [#124]
## 0.9.0.4 / 2009-01-25
* Using halt with more than 1 args causes ArgumentError [#131]
* using halt in a before filter doesn't modify response [#127]
* Add deprecated Sinatra::EventContext to unbreak plugins [#130]
* Give access to GET/POST params in filters [#129]
* Preserve non-nested params in nested params hash [#117]
* Fix backtrace dump with Rack::Lint [#116]
## 0.9.0.3 / 2009-01-21
* Fall back on mongrel then webrick when thin not found. [#75]
* Use :environment instead of :env in test helpers to
fix deprecation warnings coming from framework.
* Make sinatra/test/rspec work again [#113]
* Fix app_file detection on windows [#118]
* Fix static files with Rack::Lint in pipeline [#121]
## 0.9.0.2 / 2009-01-18
* Halting a before block should stop processing of routes [#85]
* Fix redirect/halt in before filters [#85]
## 0.9.0 / 2009-01-18
* Works with and requires Rack >= 0.9.1
* Multiple Sinatra applications can now co-exist peacefully within a
single process. The new "Sinatra::Base" class can be subclassed to
establish a blank-slate Rack application or middleware component.
Documentation on using these features is forth-coming; the following
provides the basic gist: http://gist.github.com/38605
* Parameters with subscripts are now parsed into a nested/recursive
Hash structure. e.g., "post[title]=Hello&post[body]=World" yields
params: {'post' => {'title' => 'Hello', 'body' => 'World'}}.
* Regular expressions may now be used in route patterns; captures are
available at "params[:captures]".
* New ":provides" route condition takes an array of mime types and
matches only when an Accept request header is present with a
corresponding type. [cypher]
* New request-level "pass" method; immediately exits the current block
and passes control to the next matching route.
* The request-level "body" method now takes a block; evaluation is
deferred until an attempt is made to read the body. The block must
return a String or Array.
* New "route conditions" system for attaching rules for when a route
matches. The :agent and :host route options now use this system.
* New "dump_errors" option controls whether the backtrace is dumped to
rack.errors when an exception is raised from a route. The option is
enabled by default for top-level apps.
* Better default "app_file", "root", "public", and "views" location
detection; changes to "root" and "app_file" automatically cascade to
other options that depend on them.
* Error mappings are now split into two distinct layers: exception
mappings and custom error pages. Exception mappings are registered
with "error(Exception)" and are run only when the app raises an
exception. Custom error pages are registered with "error(status_code)",
where "status_code" is an integer, and are run any time the response
has the status code specified. It's also possible to register an error
page for a range of status codes: "error(500..599)".
* In-file templates are now automatically imported from the file that
requires 'sinatra'. The use_in_file_templates! method is still available
for loading templates from other files.
* Sinatra's testing support is no longer dependent on Test::Unit. Requiring
'sinatra/test' adds the Sinatra::Test module and Sinatra::TestHarness
class, which can be used with any test framework. The 'sinatra/test/unit',
'sinatra/test/spec', 'sinatra/test/rspec', or 'sinatra/test/bacon' files
can be required to setup a framework-specific testing environment. See the
README for more information.
* Added support for Bacon (test framework). The 'sinatra/test/bacon' file
can be required to setup Sinatra test helpers on Bacon::Context.
* Deprecated "set_option" and "set_options"; use "set" instead.
* Deprecated the "env" option ("options.env"); use "environment" instead.
* Deprecated the request level "stop" method; use "halt" instead.
* Deprecated the request level "entity_tag" method; use "etag" instead.
Both "entity_tag" and "etag" were previously supported.
* Deprecated the request level "headers" method (HTTP response headers);
use "response['Header-Name']" instead.
* Deprecated "Sinatra.application"; use "Sinatra::Application" instead.
* Deprecated setting Sinatra.application = nil to reset an application.
This should no longer be necessary.
* Deprecated "Sinatra.default_options"; use
"Sinatra::Default.set(key, value)" instead.
* Deprecated the "ServerError" exception. All Exceptions are now
treated as internal server errors and result in a 500 response
status.
* Deprecated the "get_it", "post_it", "put_it", "delete_it", and "head_it"
test helper methods. Use "get", "post", "put", "delete", and "head",
respectively, instead.
* Removed Event and EventContext classes. Applications are defined in a
subclass of Sinatra::Base; each request is processed within an
instance.
## 0.3.3 / 2009-01-06
* Pin to Rack 0.4.0 (this is the last release on Rack 0.4)
* Log unhandled exception backtraces to rack.errors.
* Use RACK_ENV environment variable to establish Sinatra
environment when given. Thin sets this when started with
the -e argument.
* BUG: raising Sinatra::NotFound resulted in a 500 response
code instead of 404.
* BUG: use_in_file_templates! fails with CR/LF [#45]
* BUG: Sinatra detects the app file and root path when run under
thin/passenger.
## 0.3.2
* BUG: Static and send_file read entire file into String before
sending. Updated to stream with 8K chunks instead.
* Rake tasks and assets for building basic documentation website.
See http://sinatra.rubyforge.org
* Various minor doc fixes.
## 0.3.1
* Unbreak optional path parameters [jeremyevans]
## 0.3.0
* Add sinatra.gemspec w/ support for github gem builds. Forks can now
enable the build gem option in github to get free username-sinatra.gem
builds: gem install username-sinatra.gem --source=http://gems.github.com/
* Require rack-0.4 gem; removes frozen rack dir.
* Basic RSpec support; require 'sinatra/test/rspec' instead of
'sinatra/test/spec' to use. [avdi]
* before filters can modify request environment vars used for
routing (e.g., PATH_INFO, REQUEST_METHOD, etc.) for URL rewriting
type functionality.
* In-file templates now uses @@ instead of ## as template separator.
* Top-level environment test predicates: development?, test?, production?
* Top-level "set", "enable", and "disable" methods for tweaking
app options. [rtomayko]
* Top-level "use" method for building Rack middleware pipelines
leading to app. See README for usage. [rtomayko]
* New "reload" option - set false to disable reloading in development.
* New "host" option - host/ip to bind to [cschneid]
* New "app_file" option - override the file to reload in development
mode [cschneid]
* Development error/not_found page cleanup [sr, adamwiggins]
* Remove a bunch of core extensions (String#to_param, String#from_param,
Hash#from_params, Hash#to_params, Hash#symbolize_keys, Hash#pass)
* Various grammar and formatting fixes to README; additions on
community and contributing [cypher]
* Build RDoc using Hanna template: http://sinatrarb.rubyforge.org/api
* Specs, documentation and fixes for splat'n routes [vic]
* Fix whitespace errors across all source files. [rtomayko]
* Fix streaming issues with Mongrel (body not closed). [bmizerany]
* Fix various issues with environment not being set properly (configure
blocks not running, error pages not registering, etc.) [cypher]
* Fix to allow locals to be passed to ERB templates [cschneid]
* Fix locking issues causing random errors during reload in development.
* Fix for escaped paths not resolving static files [Matthew Walker]
## 0.2.1
* File upload fix and minor tweaks.
## 0.2.0
* Initial gem release of 0.2 codebase.
sinatra-4.1.1/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000012153 14717402212 0015757 0 ustar 00root root 0000000 0000000 # Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
sinatra-conduct@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
sinatra-4.1.1/CONTRIBUTING.md 0000664 0000000 0000000 00000011552 14717402212 0015413 0 ustar 00root root 0000000 0000000 # Contribute
Want to show Sinatra some love? Help out by contributing!
## Found a bug?
Log it in our [issue tracker][ghi] or send a note to the [mailing list][ml].
Be sure to include all relevant information, like the versions of Sinatra and
Ruby you're using. A [gist](http://gist.github.com/) of the code that caused
the issue as well as any error messages are also very helpful.
## Need help?
The [Sinatra mailing list][ml] has over 900 subscribers, many of which are happy
to help out newbies or talk about potential feature additions. You can also
drop by the [#sinatra](irc://chat.freenode.net/#sinatra) channel on
[irc.freenode.net](http://freenode.net).
## Have a patch?
Bugs and feature requests that include patches are much more likely to
get attention. Here are some guidelines that will help ensure your patch
can be applied as quickly as possible:
1. **Use [Git](http://git-scm.com) and [GitHub](http://github.com):**
The easiest way to get setup is to fork the
[sinatra/sinatra repo](http://github.com/sinatra/sinatra/).
Or, the [sinatra.github.com repo](http://github.com/sinatra/sinatra.github.com/),
if the patch is doc related.
2. **Write unit tests:** If you add or modify functionality, it must
include unit tests. If you don't write tests, we have to, and this
can hold up acceptance of the patch.
3. **Mind the `README`:** If the patch adds or modifies a major feature,
modify the `README.md` file to reflect that. Again, if you don't
update the `README`, we have to, and this holds up acceptance.
4. **Push it:** Once you're ready, push your changes to a topic branch
and add a note to the ticket with the URL to your branch. Or, say
something like, "you can find the patch on johndoe/foobranch". We also
gladly accept GitHub [pull requests](http://help.github.com/pull-requests/).
__NOTE:__ _We will take whatever we can get._ If you prefer to attach diffs in
emails to the mailing list, that's fine; but do know that _someone_ will need
to take the diff through the process described above and this can hold things
up considerably.
## Want to write docs?
The process for contributing to Sinatra's website, documentation or the book
is the same as contributing code. We use Git for versions control and GitHub to
track patch requests.
* [The sinatra.github.com repo](http://github.com/sinatra/sinatra.github.com/)
is where the website sources are managed. There are almost always people in
`#sinatra` that are happy to discuss, apply, and publish website patches.
* [The Book](http://sinatra-org-book.herokuapp.com/) has its own [Git
repository](http://github.com/sinatra/sinatra-book/) and build process but is
managed the same as the website and project codebase.
* [Sinatra Recipes](http://recipes.sinatrarb.com/) is a community
project where anyone is free to contribute ideas, recipes and tutorials. Which
also has its own [Git repository](http://github.com/sinatra/sinatra-recipes).
* [The Introduction](http://www.sinatrarb.com/intro.html) is generated from
Sinatra's [README file](http://github.com/sinatra/sinatra/blob/main/README.md).
* If you want to help translating the documentation, the README is already
available in
[Japanese](http://github.com/sinatra/sinatra/blob/main/README.ja.md),
[German](http://github.com/sinatra/sinatra/blob/main/README.de.md),
[Chinese](https://github.com/sinatra/sinatra/blob/main/README.zh.md),
[Russian](https://github.com/sinatra/sinatra/blob/main/README.ru.md),
[European](https://github.com/sinatra/sinatra/blob/main/README.pt-pt.md) and
[Brazilian](https://github.com/sinatra/sinatra/blob/main/README.pt-br.md)
Portuguese,
[French](https://github.com/sinatra/sinatra/blob/main/README.fr.md),
[Spanish](https://github.com/sinatra/sinatra/blob/main/README.es.md),
[Korean](https://github.com/sinatra/sinatra/blob/main/README.ko.md), and
[Hungarian](https://github.com/sinatra/sinatra/blob/main/README.hu.md).
The translations tend to fall behind the English version. Translations into
other languages would also be appreciated.
## Looking for something to do?
If you'd like to help out but aren't sure how, pick something that looks
interesting from the [issues][ghi] list and hack on. Make sure to leave a
comment on the ticket noting that you're investigating (a simple "Taking…" is
fine).
[ghi]: http://github.com/sinatra/sinatra/issues
[ml]: http://groups.google.com/group/sinatrarb/topics "Sinatra Mailing List"
* ["Help Wanted"](https://github.com/sinatra/sinatra/labels/help%20wanted): Anyone willing to pitch in is open to contribute to this ticket as they see fit (will try to add context / summarize or ask for requirements)
* ["Good First Issue"](https://github.com/sinatra/sinatra/labels/good%20first%20issue): Potential first time contributors should start here
* ["Wishlist"](https://github.com/sinatra/sinatra/labels/Wishlist): All the things I wish we had but have no time for
sinatra-4.1.1/Gemfile 0000664 0000000 0000000 00000003744 14717402212 0014461 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
source 'https://rubygems.org'
gemspec
gem 'rake'
rack_version = ENV['rack'].to_s
rack_version = nil if rack_version.empty? || (rack_version == 'stable')
rack_version = { github: 'rack/rack' } if rack_version == 'head'
gem 'rack', rack_version
rack_session_version = ENV['rack_session'].to_s
rack_session_version = nil if rack_session_version.empty? || (rack_session_version == 'stable')
rack_session_version = { github: 'rack/rack-session' } if rack_session_version == 'head'
gem 'rack-session', rack_session_version
gem 'rackup'
puma_version = ENV['puma'].to_s
puma_version = nil if puma_version.empty? || (puma_version == 'stable')
puma_version = { github: 'puma/puma' } if puma_version == 'head'
gem 'puma', puma_version
zeitwerk_version = ENV['zeitwerk'].to_s
zeitwerk_version = nil if zeitwerk_version.empty? || (zeitwerk_version == 'stable')
gem 'zeitwerk', zeitwerk_version
gem 'minitest', '~> 5.0'
gem 'rack-test'
gem 'rubocop', '~> 1.32.0', require: false
gem 'yard' # used by rake doc
gem 'rack-protection', path: 'rack-protection'
gem 'sinatra-contrib', path: 'sinatra-contrib'
gem 'asciidoctor'
gem 'builder'
gem 'childprocess', '>= 5'
gem 'commonmarker', '~> 0.23.4', platforms: [:ruby]
gem 'erubi'
gem 'eventmachine'
gem 'falcon', '~> 0.40', platforms: [:ruby]
gem 'haml', '~> 6'
gem 'kramdown'
gem 'liquid'
gem 'markaby'
gem 'nokogiri', '> 1.5.0'
gem 'ostruct'
gem 'pandoc-ruby', '~> 2.0.2'
gem 'rabl'
gem 'rdiscount', platforms: [:ruby]
gem 'rdoc'
gem 'redcarpet', platforms: [:ruby]
gem 'simplecov', require: false
gem 'slim', '~> 5'
gem 'yajl-ruby', platforms: [:ruby]
gem 'webrick'
# sass-embedded depends on google-protobuf
# which fails to be installed on JRuby and TruffleRuby under aarch64
# https://github.com/jruby/jruby/issues/8062
# https://github.com/protocolbuffers/protobuf/issues/11935
java = %w(jruby truffleruby).include?(RUBY_ENGINE)
aarch64 = RbConfig::CONFIG["target_cpu"] == 'aarch64'
gem 'sass-embedded', '~> 1.54' unless java && aarch64
sinatra-4.1.1/LICENSE 0000664 0000000 0000000 00000002224 14717402212 0014163 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2007, 2008, 2009 Blake Mizerany
Copyright (c) 2010-2017 Konstantin Haase
Copyright (c) 2015-2017 Zachary Scott
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
sinatra-4.1.1/MAINTENANCE.md 0000664 0000000 0000000 00000002370 14717402212 0015224 0 ustar 00root root 0000000 0000000 # Sinatra maintenance
## Versions
### Releases
The next major version of Sinatra will be released from the main branch. Each version will be tagged so it will be possible to branch of should there be a need for bug fixes and other updates.
## Issues
### New features
New features will only be added to the main branch and will not be made available in point releases.
### Bug fixes
Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from.
### Security issues
The current release series will receive patches and new versions in case of a security issue.
### Severe security issues
For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team.
### Unsupported Release Series
When a release series is no longer supported, it’s your own responsibility to deal with bugs and security issues. We may provide back-ports of the fixes and publish them to git, however there will be no new versions released. If you are not comfortable maintaining your own versions, you should upgrade to a supported version.
sinatra-4.1.1/README.md 0000664 0000000 0000000 00000220754 14717402212 0014447 0 ustar 00root root 0000000 0000000 # Sinatra
[](https://badge.fury.io/rb/sinatra)
[](https://github.com/sinatra/sinatra/actions/workflows/test.yml)
Sinatra is a [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) for
quickly creating web applications in Ruby with minimal effort:
```ruby
# myapp.rb
require 'sinatra'
get '/' do
'Hello world!'
end
```
Install the gems needed:
```shell
gem install sinatra rackup puma
```
And run with:
```shell
ruby myapp.rb
```
View at: [http://localhost:4567](http://localhost:4567)
The code you changed will not take effect until you restart the server.
Please restart the server every time you change or use a code reloader
like [rerun](https://github.com/alexch/rerun) or
[rack-unreloader](https://github.com/jeremyevans/rack-unreloader).
It is recommended to also run `gem install puma`, which Sinatra will
pick up if available.
## Table of Contents
- [Sinatra](#sinatra)
- [Table of Contents](#table-of-contents)
- [Routes](#routes)
- [Conditions](#conditions)
- [Return Values](#return-values)
- [Custom Route Matchers](#custom-route-matchers)
- [Static Files](#static-files)
- [Views / Templates](#views--templates)
- [Literal Templates](#literal-templates)
- [Available Template Languages](#available-template-languages)
- [Haml Templates](#haml-templates)
- [Erb Templates](#erb-templates)
- [Builder Templates](#builder-templates)
- [Nokogiri Templates](#nokogiri-templates)
- [Sass Templates](#sass-templates)
- [Scss Templates](#scss-templates)
- [Liquid Templates](#liquid-templates)
- [Markdown Templates](#markdown-templates)
- [RDoc Templates](#rdoc-templates)
- [AsciiDoc Templates](#asciidoc-templates)
- [Markaby Templates](#markaby-templates)
- [RABL Templates](#rabl-templates)
- [Slim Templates](#slim-templates)
- [Yajl Templates](#yajl-templates)
- [Accessing Variables in Templates](#accessing-variables-in-templates)
- [Templates with `yield` and nested layouts](#templates-with-yield-and-nested-layouts)
- [Inline Templates](#inline-templates)
- [Named Templates](#named-templates)
- [Associating File Extensions](#associating-file-extensions)
- [Adding Your Own Template Engine](#adding-your-own-template-engine)
- [Using Custom Logic for Template Lookup](#using-custom-logic-for-template-lookup)
- [Filters](#filters)
- [Helpers](#helpers)
- [Using Sessions](#using-sessions)
- [Session Secret Security](#session-secret-security)
- [Session Config](#session-config)
- [Choosing Your Own Session Middleware](#choosing-your-own-session-middleware)
- [Halting](#halting)
- [Passing](#passing)
- [Triggering Another Route](#triggering-another-route)
- [Setting Body, Status Code, and Headers](#setting-body-status-code-and-headers)
- [Streaming Responses](#streaming-responses)
- [Logging](#logging)
- [Mime Types](#mime-types)
- [Generating URLs](#generating-urls)
- [Browser Redirect](#browser-redirect)
- [Cache Control](#cache-control)
- [Sending Files](#sending-files)
- [Accessing the Request Object](#accessing-the-request-object)
- [Attachments](#attachments)
- [Dealing with Date and Time](#dealing-with-date-and-time)
- [Looking Up Template Files](#looking-up-template-files)
- [Configuration](#configuration)
- [Configuring attack protection](#configuring-attack-protection)
- [Available Settings](#available-settings)
- [Lifecycle Events](#lifecycle-events)
- [Environments](#environments)
- [Error Handling](#error-handling)
- [Not Found](#not-found)
- [Error](#error)
- [Rack Middleware](#rack-middleware)
- [Testing](#testing)
- [Sinatra::Base - Middleware, Libraries, and Modular Apps](#sinatrabase---middleware-libraries-and-modular-apps)
- [Modular vs. Classic Style](#modular-vs-classic-style)
- [Serving a Modular Application](#serving-a-modular-application)
- [Using a Classic Style Application with a config.ru](#using-a-classic-style-application-with-a-configru)
- [When to use a config.ru?](#when-to-use-a-configru)
- [Using Sinatra as Middleware](#using-sinatra-as-middleware)
- [Dynamic Application Creation](#dynamic-application-creation)
- [Scopes and Binding](#scopes-and-binding)
- [Application/Class Scope](#applicationclass-scope)
- [Request/Instance Scope](#requestinstance-scope)
- [Delegation Scope](#delegation-scope)
- [Command Line](#command-line)
- [Multi-threading](#multi-threading)
- [Requirement](#requirement)
- [The Bleeding Edge](#the-bleeding-edge)
- [With Bundler](#with-bundler)
- [Versioning](#versioning)
- [Further Reading](#further-reading)
## Routes
In Sinatra, a route is an HTTP method paired with a URL-matching pattern.
Each route is associated with a block:
```ruby
get '/' do
.. show something ..
end
post '/' do
.. create something ..
end
put '/' do
.. replace something ..
end
patch '/' do
.. modify something ..
end
delete '/' do
.. annihilate something ..
end
options '/' do
.. appease something ..
end
link '/' do
.. affiliate something ..
end
unlink '/' do
.. separate something ..
end
```
Routes are matched in the order they are defined. The first route that
matches the request is invoked.
Routes with trailing slashes are different from the ones without:
```ruby
get '/foo' do
# Does not match "GET /foo/"
end
```
Route patterns may include named parameters, accessible via the
`params` hash:
```ruby
get '/hello/:name' do
# matches "GET /hello/foo" and "GET /hello/bar"
# params['name'] is 'foo' or 'bar'
"Hello #{params['name']}!"
end
```
You can also access named parameters via block parameters:
```ruby
get '/hello/:name' do |n|
# matches "GET /hello/foo" and "GET /hello/bar"
# params['name'] is 'foo' or 'bar'
# n stores params['name']
"Hello #{n}!"
end
```
Route patterns may also include splat (or wildcard) parameters, accessible
via the `params['splat']` array:
```ruby
get '/say/*/to/*' do
# matches /say/hello/to/world
params['splat'] # => ["hello", "world"]
end
get '/download/*.*' do
# matches /download/path/to/file.xml
params['splat'] # => ["path/to/file", "xml"]
end
```
Or with block parameters:
```ruby
get '/download/*.*' do |path, ext|
[path, ext] # => ["path/to/file", "xml"]
end
```
Route matching with Regular Expressions:
```ruby
get /\/hello\/([\w]+)/ do
"Hello, #{params['captures'].first}!"
end
```
Or with a block parameter:
```ruby
get %r{/hello/([\w]+)} do |c|
# Matches "GET /meta/hello/world", "GET /hello/world/1234" etc.
"Hello, #{c}!"
end
```
Route patterns may have optional parameters:
```ruby
get '/posts/:format?' do
# matches "GET /posts/" and any extension "GET /posts/json", "GET /posts/xml" etc
end
```
Routes may also utilize query parameters:
```ruby
get '/posts' do
# matches "GET /posts?title=foo&author=bar"
title = params['title']
author = params['author']
# uses title and author variables; query is optional to the /posts route
end
```
By the way, unless you disable the path traversal attack protection (see
[below](#configuring-attack-protection)), the request path might be modified before
matching against your routes.
You may customize the [Mustermann](https://github.com/sinatra/mustermann#readme)
options used for a given route by passing in a `:mustermann_opts` hash:
```ruby
get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do
# matches /posts exactly, with explicit anchoring
"If you match an anchored pattern clap your hands!"
end
```
It looks like a [condition](#conditions), but it isn't one! These options will
be merged into the global `:mustermann_opts` hash described
[below](#available-settings).
## Conditions
Routes may include a variety of matching conditions, such as the user agent:
```ruby
get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
"You're using Songbird version #{params['agent'][0]}"
end
get '/foo' do
# Matches non-songbird browsers
end
```
Other available conditions are `host_name` and `provides`:
```ruby
get '/', :host_name => /^admin\./ do
"Admin Area, Access denied!"
end
get '/', :provides => 'html' do
haml :index
end
get '/', :provides => ['rss', 'atom', 'xml'] do
builder :feed
end
```
`provides` searches the request's Accept header.
You can easily define your own conditions:
```ruby
set(:probability) { |value| condition { rand <= value } }
get '/win_a_car', :probability => 0.1 do
"You won!"
end
get '/win_a_car' do
"Sorry, you lost."
end
```
For a condition that takes multiple values use a splat:
```ruby
set(:auth) do |*roles| # <- notice the splat here
condition do
unless logged_in? && roles.any? {|role| current_user.in_role? role }
redirect "/login/", 303
end
end
end
get "/my/account/", :auth => [:user, :admin] do
"Your Account Details"
end
get "/only/admin/", :auth => :admin do
"Only admins are allowed here!"
end
```
## Return Values
The return value of a route block determines at least the response body
passed on to the HTTP client or at least the next middleware in the
Rack stack. Most commonly, this is a string, as in the above examples.
But other values are also accepted.
You can return an object that would either be a valid Rack response, Rack
body object or HTTP status code:
* An Array with three elements: `[status (Integer), headers (Hash), response
body (responds to #each)]`
* An Array with two elements: `[status (Integer), response body (responds to
#each)]`
* An object that responds to `#each` and passes nothing but strings to
the given block
* A Integer representing the status code
That way we can, for instance, easily implement a streaming example:
```ruby
class Stream
def each
100.times { |i| yield "#{i}\n" }
end
end
get('/') { Stream.new }
```
You can also use the `stream` helper method ([described below](#streaming-responses)) to reduce
boilerplate and embed the streaming logic in the route.
## Custom Route Matchers
As shown above, Sinatra ships with built-in support for using String
patterns and regular expressions as route matches. However, it does not
stop there. You can easily define your own matchers:
```ruby
class AllButPattern
def initialize(except)
@except = except
end
def to_pattern(options)
return self
end
def params(route)
return {} unless @except === route
end
end
def all_but(pattern)
AllButPattern.new(pattern)
end
get all_but("/index") do
# ...
end
```
Note that the above example might be over-engineered, as it can also be
expressed as:
```ruby
get /.*/ do
pass if request.path_info == "/index"
# ...
end
```
## Static Files
Static files are served from the `./public` directory. You can specify
a different location by setting the `:public_folder` option:
```ruby
set :public_folder, __dir__ + '/static'
```
Note that the public directory name is not included in the URL. A file
`./public/css/style.css` is made available as
`http://example.com/css/style.css`.
Use the `:static_cache_control` setting (see [below](#cache-control)) to add
`Cache-Control` header info.
## Views / Templates
Each template language is exposed via its own rendering method. These
methods simply return a string:
```ruby
get '/' do
erb :index
end
```
This renders `views/index.erb`.
Instead of a template name, you can also just pass in the template content
directly:
```ruby
get '/' do
code = "<%= Time.now %>"
erb code
end
```
Templates take a second argument, the options hash:
```ruby
get '/' do
erb :index, :layout => :post
end
```
This will render `views/index.erb` embedded in the
`views/post.erb` (default is `views/layout.erb`, if it exists).
Any options not understood by Sinatra will be passed on to the template
engine:
```ruby
get '/' do
haml :index, :format => :html5
end
```
You can also set options per template language in general:
```ruby
set :haml, :format => :html5
get '/' do
haml :index
end
```
Options passed to the render method override options set via `set`.
Available Options:
- locals
-
List of locals passed to the document. Handy with partials.
Example: erb "<%= foo %>", :locals => {:foo => "bar"}
- default_encoding
-
String encoding to use if uncertain. Defaults to
settings.default_encoding.
- views
-
Views folder to load templates from. Defaults to settings.views.
- layout
-
Whether to use a layout (true or false). If it's a
Symbol, specifies what template to use. Example:
erb :index, :layout => !request.xhr?
- content_type
-
Content-Type the template produces. Default depends on template language.
- scope
-
Scope to render template under. Defaults to the application
instance. If you change this, instance variables and helper methods
will not be available.
- layout_engine
-
Template engine to use for rendering the layout. Useful for
languages that do not support layouts otherwise. Defaults to the
engine used for the template. Example: set :rdoc, :layout_engine
=> :erb
- layout_options
-
Special options only used for rendering the layout. Example:
set :rdoc, :layout_options => { :views => 'views/layouts' }
Templates are assumed to be located directly under the `./views`
directory. To use a different views directory:
```ruby
set :views, settings.root + '/templates'
```
One important thing to remember is that you always have to reference
templates with symbols, even if they're in a subdirectory (in this case,
use: `:'subdir/template'` or `'subdir/template'.to_sym`). You must use a
symbol because otherwise rendering methods will render any strings
passed to them directly.
### Literal Templates
```ruby
get '/' do
haml '%div.title Hello World'
end
```
Renders the template string. You can optionally specify `:path` and
`:line` for a clearer backtrace if there is a filesystem path or line
associated with that string:
```ruby
get '/' do
haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3
end
```
### Available Template Languages
Some languages have multiple implementations. To specify what implementation
to use (and to be thread-safe), you should simply require it first:
```ruby
require 'rdiscount'
get('/') { markdown :index }
```
#### Haml Templates
Dependency |
haml |
File Extension |
.haml |
Example |
haml :index, :format => :html5 |
#### Erb Templates
Dependency |
erubi
or erb (included in Ruby)
|
File Extensions |
.erb, .rhtml or .erubi (Erubi only) |
Example |
erb :index |
#### Builder Templates
Dependency |
builder
|
File Extension |
.builder |
Example |
builder { |xml| xml.em "hi" } |
It also takes a block for inline templates (see [example](#inline-templates)).
#### Nokogiri Templates
Dependency |
nokogiri |
File Extension |
.nokogiri |
Example |
nokogiri { |xml| xml.em "hi" } |
It also takes a block for inline templates (see [example](#inline-templates)).
#### Sass Templates
Dependency |
sass-embedded |
File Extension |
.sass |
Example |
sass :stylesheet, :style => :expanded |
#### Scss Templates
Dependency |
sass-embedded |
File Extension |
.scss |
Example |
scss :stylesheet, :style => :expanded |
#### Liquid Templates
Dependency |
liquid |
File Extension |
.liquid |
Example |
liquid :index, :locals => { :key => 'value' } |
Since you cannot call Ruby methods (except for `yield`) from a Liquid
template, you almost always want to pass locals to it.
#### Markdown Templates
It is not possible to call methods from Markdown, nor to pass locals to it.
You therefore will usually use it in combination with another rendering
engine:
```ruby
erb :overview, :locals => { :text => markdown(:introduction) }
```
Note that you may also call the `markdown` method from within other
templates:
```ruby
%h1 Hello From Haml!
%p= markdown(:greetings)
```
Since you cannot call Ruby from Markdown, you cannot use layouts written in
Markdown. However, it is possible to use another rendering engine for the
template than for the layout by passing the `:layout_engine` option.
#### RDoc Templates
Dependency |
RDoc |
File Extension |
.rdoc |
Example |
rdoc :README, :layout_engine => :erb |
It is not possible to call methods from RDoc, nor to pass locals to it. You
therefore will usually use it in combination with another rendering engine:
```ruby
erb :overview, :locals => { :text => rdoc(:introduction) }
```
Note that you may also call the `rdoc` method from within other templates:
```ruby
%h1 Hello From Haml!
%p= rdoc(:greetings)
```
Since you cannot call Ruby from RDoc, you cannot use layouts written in
RDoc. However, it is possible to use another rendering engine for the
template than for the layout by passing the `:layout_engine` option.
#### AsciiDoc Templates
Dependency |
Asciidoctor |
File Extension |
.asciidoc, .adoc and .ad |
Example |
asciidoc :README, :layout_engine => :erb |
Since you cannot call Ruby methods directly from an AsciiDoc template, you
almost always want to pass locals to it.
#### Markaby Templates
Dependency |
Markaby |
File Extension |
.mab |
Example |
markaby { h1 "Welcome!" } |
It also takes a block for inline templates (see [example](#inline-templates)).
#### RABL Templates
Dependency |
Rabl |
File Extension |
.rabl |
Example |
rabl :index |
#### Slim Templates
Dependency |
Slim Lang |
File Extension |
.slim |
Example |
slim :index |
#### Yajl Templates
Dependency |
yajl-ruby |
File Extension |
.yajl |
Example |
yajl :index,
:locals => { :key => 'qux' },
:callback => 'present',
:variable => 'resource'
|
The template source is evaluated as a Ruby string, and the
resulting json variable is converted using `#to_json`:
```ruby
json = { :foo => 'bar' }
json[:baz] = key
```
The `:callback` and `:variable` options can be used to decorate the rendered
object:
```javascript
var resource = {"foo":"bar","baz":"qux"};
present(resource);
```
### Accessing Variables in Templates
Templates are evaluated within the same context as route handlers. Instance
variables set in route handlers are directly accessible by templates:
```ruby
get '/:id' do
@foo = Foo.find(params['id'])
haml '%h1= @foo.name'
end
```
Or, specify an explicit Hash of local variables:
```ruby
get '/:id' do
foo = Foo.find(params['id'])
haml '%h1= bar.name', :locals => { :bar => foo }
end
```
This is typically used when rendering templates as partials from within
other templates.
### Templates with `yield` and nested layouts
A layout is usually just a template that calls `yield`.
Such a template can be used either through the `:template` option as
described above, or it can be rendered with a block as follows:
```ruby
erb :post, :layout => false do
erb :index
end
```
This code is mostly equivalent to `erb :index, :layout => :post`.
Passing blocks to rendering methods is most useful for creating nested
layouts:
```ruby
erb :main_layout, :layout => false do
erb :admin_layout do
erb :user
end
end
```
This can also be done in fewer lines of code with:
```ruby
erb :admin_layout, :layout => :main_layout do
erb :user
end
```
Currently, the following rendering methods accept a block: `erb`, `haml`,
`liquid`, `slim `. Also, the general `render` method accepts a block.
### Inline Templates
Templates may be defined at the end of the source file:
```ruby
require 'sinatra'
get '/' do
haml :index
end
__END__
@@ layout
%html
!= yield
@@ index
%div.title Hello world.
```
NOTE: Inline templates defined in the source file that requires Sinatra are
automatically loaded. Call `enable :inline_templates` explicitly if you
have inline templates in other source files.
### Named Templates
Templates may also be defined using the top-level `template` method:
```ruby
template :layout do
"%html\n =yield\n"
end
template :index do
'%div.title Hello World!'
end
get '/' do
haml :index
end
```
If a template named "layout" exists, it will be used each time a template
is rendered. You can individually disable layouts by passing
`:layout => false` or disable them by default via
`set :haml, :layout => false`:
```ruby
get '/' do
haml :index, :layout => !request.xhr?
end
```
### Associating File Extensions
To associate a file extension with a template engine, use
`Tilt.register`. For instance, if you like to use the file extension
`tt` for Haml templates, you can do the following:
```ruby
Tilt.register Tilt[:haml], :tt
```
### Adding Your Own Template Engine
First, register your engine with Tilt, then create a rendering method:
```ruby
Tilt.register MyAwesomeTemplateEngine, :myat
helpers do
def myat(*args) render(:myat, *args) end
end
get '/' do
myat :index
end
```
Renders `./views/index.myat`. Learn more about
[Tilt](https://github.com/rtomayko/tilt#readme).
### Using Custom Logic for Template Lookup
To implement your own template lookup mechanism you can write your
own `#find_template` method:
```ruby
configure do
set :views, [ './views/a', './views/b' ]
end
def find_template(views, name, engine, &block)
Array(views).each do |v|
super(v, name, engine, &block)
end
end
```
## Filters
Before filters are evaluated before each request within the same context
as the routes will be and can modify the request and response. Instance
variables set in filters are accessible by routes and templates:
```ruby
before do
@note = 'Hi!'
request.path_info = '/foo/bar/baz'
end
get '/foo/*' do
@note #=> 'Hi!'
params['splat'] #=> 'bar/baz'
end
```
After filters are evaluated after each request within the same context
as the routes will be and can also modify the request and response.
Instance variables set in before filters and routes are accessible by
after filters:
```ruby
after do
puts response.status
end
```
Note: Unless you use the `body` method rather than just returning a
String from the routes, the body will not yet be available in the after
filter, since it is generated later on.
Filters optionally take a pattern, causing them to be evaluated only if the
request path matches that pattern:
```ruby
before '/protected/*' do
authenticate!
end
after '/create/:slug' do |slug|
session[:last_slug] = slug
end
```
Like routes, filters also take conditions:
```ruby
before :agent => /Songbird/ do
# ...
end
after '/blog/*', :host_name => 'example.com' do
# ...
end
```
## Helpers
Use the top-level `helpers` method to define helper methods for use in
route handlers and templates:
```ruby
helpers do
def bar(name)
"#{name}bar"
end
end
get '/:name' do
bar(params['name'])
end
```
Alternatively, helper methods can be separately defined in a module:
```ruby
module FooUtils
def foo(name) "#{name}foo" end
end
module BarUtils
def bar(name) "#{name}bar" end
end
helpers FooUtils, BarUtils
```
The effect is the same as including the modules in the application class.
### Using Sessions
A session is used to keep state during requests. If activated, you have one
session hash per user session:
```ruby
enable :sessions
get '/' do
"value = " << session[:value].inspect
end
get '/:value' do
session['value'] = params['value']
end
```
#### Session Secret Security
To improve security, the session data in the cookie is signed with a session
secret using `HMAC-SHA1`. This session secret should optimally be a
cryptographically secure random value of an appropriate length which for
`HMAC-SHA1` is greater than or equal to 64 bytes (512 bits, 128 hex
characters). You would be advised not to use a secret that is less than 32
bytes of randomness (256 bits, 64 hex characters). It is therefore **very
important** that you don't just make the secret up, but instead use a secure
random number generator to create it. Humans are extremely bad at generating
random values.
By default, a 32 byte secure random session secret is generated for you by
Sinatra, but it will change with every restart of your application. If you
have multiple instances of your application, and you let Sinatra generate the
key, each instance would then have a different session key which is probably
not what you want.
For better security and usability it's
[recommended](https://12factor.net/config) that you generate a secure random
secret and store it in an environment variable on each host running your
application so that all of your application instances will share the same
secret. You should periodically rotate this session secret to a new value.
Here are some examples of how you might create a 64-byte secret and set it:
**Session Secret Generation**
```text
$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Session Secret Environment Variable**
Set a `SESSION_SECRET` environment variable for Sinatra to the value you
generated. Make this value persistent across reboots of your host. Since the
method for doing this will vary across systems this is for illustrative
purposes only:
```bash
# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc
```
**Session Secret App Config**
Set up your app config to fail-safe to a secure random secret
if the `SESSION_SECRET` environment variable is not available:
```ruby
require 'securerandom'
set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) }
```
#### Session Config
If you want to configure it further, you may also store a hash with options
in the `sessions` setting:
```ruby
set :sessions, :domain => 'foo.com'
```
To share your session across other apps on subdomains of foo.com, prefix the
domain with a *.* like this instead:
```ruby
set :sessions, :domain => '.foo.com'
```
#### Choosing Your Own Session Middleware
Note that `enable :sessions` actually stores all data in a cookie. This
might not always be what you want (storing lots of data will increase your
traffic, for instance). You can use any Rack session middleware in order to
do so, one of the following methods can be used:
```ruby
enable :sessions
set :session_store, Rack::Session::Pool
```
Or to set up sessions with a hash of options:
```ruby
set :sessions, :expire_after => 2592000
set :session_store, Rack::Session::Pool
```
Another option is to **not** call `enable :sessions`, but instead pull in
your middleware of choice as you would any other middleware.
It is important to note that when using this method, session based
protection **will not be enabled by default**.
The Rack middleware to do that will also need to be added:
```ruby
use Rack::Session::Pool, :expire_after => 2592000
use Rack::Protection::RemoteToken
use Rack::Protection::SessionHijacking
```
See '[Configuring attack protection](#configuring-attack-protection)' for more information.
### Halting
To immediately stop a request within a filter or route use:
```ruby
halt
```
You can also specify the status when halting:
```ruby
halt 410
```
Or the body:
```ruby
halt 'this will be the body'
```
Or both:
```ruby
halt 401, 'go away!'
```
With headers:
```ruby
halt 402, {'Content-Type' => 'text/plain'}, 'revenge'
```
It is of course possible to combine a template with `halt`:
```ruby
halt erb(:error)
```
### Passing
A route can punt processing to the next matching route using `pass`:
```ruby
get '/guess/:who' do
pass unless params['who'] == 'Frank'
'You got me!'
end
get '/guess/*' do
'You missed!'
end
```
The route block is immediately exited and control continues with the next
matching route. If no matching route is found, a 404 is returned.
### Triggering Another Route
Sometimes `pass` is not what you want, instead, you would like to get the
result of calling another route. Simply use `call` to achieve this:
```ruby
get '/foo' do
status, headers, body = call env.merge("PATH_INFO" => '/bar')
[status, headers, body.map(&:upcase)]
end
get '/bar' do
"bar"
end
```
Note that in the example above, you would ease testing and increase
performance by simply moving `"bar"` into a helper used by both `/foo` and
`/bar`.
If you want the request to be sent to the same application instance rather
than a duplicate, use `call!` instead of `call`.
Check out the Rack specification if you want to learn more about `call`.
### Setting Body, Status Code, and Headers
It is possible and recommended to set the status code and response body with
the return value of the route block. However, in some scenarios, you might
want to set the body at an arbitrary point in the execution flow. You can do
so with the `body` helper method. If you do so, you can use that method from
thereon to access the body:
```ruby
get '/foo' do
body "bar"
end
after do
puts body
end
```
It is also possible to pass a block to `body`, which will be executed by the
Rack handler (this can be used to implement streaming, [see "Return Values"](#return-values)).
Similar to the body, you can also set the status code and headers:
```ruby
get '/foo' do
status 418
headers \
"Allow" => "BREW, POST, GET, PROPFIND, WHEN",
"Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt"
body "I'm a teapot!"
end
```
Like `body`, `headers` and `status` with no arguments can be used to access
their current values.
### Streaming Responses
Sometimes you want to start sending out data while still generating parts of
the response body. In extreme examples, you want to keep sending data until
the client closes the connection. You can use the `stream` helper to avoid
creating your own wrapper:
```ruby
get '/' do
stream do |out|
out << "It's gonna be legen -\n"
sleep 0.5
out << " (wait for it) \n"
sleep 1
out << "- dary!\n"
end
end
```
This allows you to implement streaming APIs,
[Server Sent Events](https://w3c.github.io/eventsource/), and can be used as
the basis for [WebSockets](https://en.wikipedia.org/wiki/WebSocket). It can
also be used to increase throughput if some but not all content depends on a
slow resource.
Note that the streaming behavior, especially the number of concurrent
requests, highly depends on the webserver used to serve the application.
Some servers might not even support streaming at all. If the server does not
support streaming, the body will be sent all at once after the block passed
to `stream` finishes executing. Streaming does not work at all with Shotgun.
If the optional parameter is set to `keep_open`, it will not call `close` on
the stream object, allowing you to close it at any later point in the
execution flow.
You can have a look at the [chat example](https://github.com/sinatra/sinatra/blob/main/examples/chat.rb)
It's also possible for the client to close the connection when trying to
write to the socket. Because of this, it's recommended to check
`out.closed?` before trying to write.
### Logging
In the request scope, the `logger` helper exposes a `Logger` instance:
```ruby
get '/' do
logger.info "loading data"
# ...
end
```
This logger will automatically take your Rack handler's logging settings into
account. If logging is disabled, this method will return a dummy object, so
you do not have to worry about it in your routes and filters.
Note that logging is only enabled for `Sinatra::Application` by default, so
if you inherit from `Sinatra::Base`, you probably want to enable it yourself:
```ruby
class MyApp < Sinatra::Base
configure :production, :development do
enable :logging
end
end
```
To avoid any logging middleware to be set up, set the `logging` option to
`nil`. However, keep in mind that `logger` will in that case return `nil`. A
common use case is when you want to set your own logger. Sinatra will use
whatever it will find in `env['rack.logger']`.
### Mime Types
When using `send_file` or static files you may have mime types Sinatra
doesn't understand. Use `mime_type` to register them by file extension:
```ruby
configure do
mime_type :foo, 'text/foo'
end
```
You can also use it with the `content_type` helper:
```ruby
get '/' do
content_type :foo
"foo foo foo"
end
```
### Generating URLs
For generating URLs you should use the `url` helper method, for instance, in
Haml:
```ruby
%a{:href => url('/foo')} foo
```
It takes reverse proxies and Rack routers into account - if present.
This method is also aliased to `to` (see [below](#browser-redirect) for an example).
### Browser Redirect
You can trigger a browser redirect with the `redirect` helper method:
```ruby
get '/foo' do
redirect to('/bar')
end
```
Any additional parameters are handled like arguments passed to `halt`:
```ruby
redirect to('/bar'), 303
redirect 'http://www.google.com/', 'wrong place, buddy'
```
You can also easily redirect back to the page the user came from with
`redirect back`:
```ruby
get '/foo' do
"do something"
end
get '/bar' do
do_something
redirect back
end
```
To pass arguments with a redirect, either add them to the query:
```ruby
redirect to('/bar?sum=42')
```
Or use a session:
```ruby
enable :sessions
get '/foo' do
session[:secret] = 'foo'
redirect to('/bar')
end
get '/bar' do
session[:secret]
end
```
### Cache Control
Setting your headers correctly is the foundation for proper HTTP caching.
You can easily set the Cache-Control header like this:
```ruby
get '/' do
cache_control :public
"cache it!"
end
```
Pro tip: Set up caching in a before filter:
```ruby
before do
cache_control :public, :must_revalidate, :max_age => 60
end
```
If you are using the `expires` helper to set the corresponding header,
`Cache-Control` will be set automatically for you:
```ruby
before do
expires 500, :public, :must_revalidate
end
```
To properly use caches, you should consider using `etag` or `last_modified`.
It is recommended to call those helpers *before* doing any heavy lifting, as
they will immediately flush a response if the client already has the current
version in its cache:
```ruby
get "/article/:id" do
@article = Article.find params['id']
last_modified @article.updated_at
etag @article.sha1
erb :article
end
```
It is also possible to use a
[weak ETag](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation):
```ruby
etag @article.sha1, :weak
```
These helpers will not do any caching for you, but rather feed the necessary
information to your cache. If you are looking for a quick
reverse-proxy caching solution, try
[rack-cache](https://github.com/rtomayko/rack-cache#readme):
```ruby
require "rack/cache"
require "sinatra"
use Rack::Cache
get '/' do
cache_control :public, :max_age => 36000
sleep 5
"hello"
end
```
Use the `:static_cache_control` setting (see [below](#cache-control)) to add
`Cache-Control` header info to static files.
According to RFC 2616, your application should behave differently if the
If-Match or If-None-Match header is set to `*`, depending on whether the
resource requested is already in existence. Sinatra assumes resources for
safe (like get) and idempotent (like put) requests are already in existence,
whereas other resources (for instance post requests) are treated as new
resources. You can change this behavior by passing in a `:new_resource`
option:
```ruby
get '/create' do
etag '', :new_resource => true
Article.create
erb :new_article
end
```
If you still want to use a weak ETag, pass in a `:kind` option:
```ruby
etag '', :new_resource => true, :kind => :weak
```
### Sending Files
To return the contents of a file as the response, you can use the `send_file`
helper method:
```ruby
get '/' do
send_file 'foo.png'
end
```
It also takes options:
```ruby
send_file 'foo.png', :type => :jpg
```
The options are:
- filename
- File name to be used in the response,
defaults to the real file name.
- last_modified
- Value for Last-Modified header, defaults to the file's mtime.
- type
- Value for Content-Type header, guessed from the file extension if
missing.
- disposition
-
Value for Content-Disposition header, possible values: nil
(default), :attachment and :inline
- length
- Value for Content-Length header, defaults to file size.
- status
-
Status code to be sent. Useful when sending a static file as an error
page. If supported by the Rack handler, other means than streaming
from the Ruby process will be used. If you use this helper method,
Sinatra will automatically handle range requests.
### Accessing the Request Object
The incoming request object can be accessed from request level (filter,
routes, error handlers) through the `request` method:
```ruby
# app running on http://example.com/example
get '/foo' do
t = %w[text/css text/html application/javascript]
request.accept # ['text/html', '*/*']
request.accept? 'text/xml' # true
request.preferred_type(t) # 'text/html'
request.body # request body sent by the client (see below)
request.scheme # "http"
request.script_name # "/example"
request.path_info # "/foo"
request.port # 80
request.request_method # "GET"
request.query_string # ""
request.content_length # length of request.body
request.media_type # media type of request.body
request.host # "example.com"
request.get? # true (similar methods for other verbs)
request.form_data? # false
request["some_param"] # value of some_param parameter. [] is a shortcut to the params hash.
request.referrer # the referrer of the client or '/'
request.user_agent # user agent (used by :agent condition)
request.cookies # hash of browser cookies
request.xhr? # is this an ajax request?
request.url # "http://example.com/example/foo"
request.path # "/example/foo"
request.ip # client IP address
request.secure? # false (would be true over ssl)
request.forwarded? # true (if running behind a reverse proxy)
request.env # raw env hash handed in by Rack
end
```
Some options, like `script_name` or `path_info`, can also be written:
```ruby
before { request.path_info = "/" }
get "/" do
"all requests end up here"
end
```
The `request.body` is an IO or StringIO object:
```ruby
post "/api" do
request.body.rewind # in case someone already read it
data = JSON.parse request.body.read
"Hello #{data['name']}!"
end
```
### Attachments
You can use the `attachment` helper to tell the browser the response should
be stored on disk rather than displayed in the browser:
```ruby
get '/' do
attachment
"store it!"
end
```
You can also pass it a file name:
```ruby
get '/' do
attachment "info.txt"
"store it!"
end
```
### Dealing with Date and Time
Sinatra offers a `time_for` helper method that generates a Time object from
the given value. It is also able to convert `DateTime`, `Date` and similar
classes:
```ruby
get '/' do
pass if Time.now > time_for('Dec 23, 2016')
"still time"
end
```
This method is used internally by `expires`, `last_modified` and akin. You
can therefore easily extend the behavior of those methods by overriding
`time_for` in your application:
```ruby
helpers do
def time_for(value)
case value
when :yesterday then Time.now - 24*60*60
when :tomorrow then Time.now + 24*60*60
else super
end
end
end
get '/' do
last_modified :yesterday
expires :tomorrow
"hello"
end
```
### Looking Up Template Files
The `find_template` helper is used to find template files for rendering:
```ruby
find_template settings.views, 'foo', Tilt[:haml] do |file|
puts "could be #{file}"
end
```
This is not really useful. But it is useful that you can actually override
this method to hook in your own lookup mechanism. For instance, if you want
to be able to use more than one view directory:
```ruby
set :views, ['views', 'templates']
helpers do
def find_template(views, name, engine, &block)
Array(views).each { |v| super(v, name, engine, &block) }
end
end
```
Another example would be using different directories for different engines:
```ruby
set :views, :haml => 'templates', :default => 'views'
helpers do
def find_template(views, name, engine, &block)
_, folder = views.detect { |k,v| engine == Tilt[k] }
folder ||= views[:default]
super(folder, name, engine, &block)
end
end
```
You can also easily wrap this up in an extension and share it with others!
Note that `find_template` does not check if the file really exists but
rather calls the given block for all possible paths. This is not a
performance issue, since `render` will use `break` as soon as a file is
found. Also, template locations (and content) will be cached if you are not
running in development mode. You should keep that in mind if you write a
really crazy method.
## Configuration
Run once, at startup, in any environment:
```ruby
configure do
# setting one option
set :option, 'value'
# setting multiple options
set :a => 1, :b => 2
# same as `set :option, true`
enable :option
# same as `set :option, false`
disable :option
# you can also have dynamic settings with blocks
set(:css_dir) { File.join(views, 'css') }
end
```
Run only when the environment (`APP_ENV` environment variable) is set to
`:production`:
```ruby
configure :production do
...
end
```
Run when the environment is set to either `:production` or `:test`:
```ruby
configure :production, :test do
...
end
```
You can access those options via `settings`:
```ruby
configure do
set :foo, 'bar'
end
get '/' do
settings.foo? # => true
settings.foo # => 'bar'
...
end
```
### Configuring attack protection
Sinatra is using
[Rack::Protection](https://github.com/sinatra/sinatra/tree/main/rack-protection#readme) to
defend your application against common, opportunistic attacks. You can
easily disable this behavior (which will open up your application to tons
of common vulnerabilities):
```ruby
disable :protection
```
To skip a single defense layer, set `protection` to an options hash:
```ruby
set :protection, :except => :path_traversal
```
You can also hand in an array in order to disable a list of protections:
```ruby
set :protection, :except => [:path_traversal, :remote_token]
```
By default, Sinatra will only set up session based protection if `:sessions`
have been enabled. See '[Using Sessions](#using-sessions)'. Sometimes you may want to set up
sessions "outside" of the Sinatra app, such as in the config.ru or with a
separate `Rack::Builder` instance. In that case, you can still set up session
based protection by passing the `:session` option:
```ruby
set :protection, :session => true
```
### Available Settings
- absolute_redirects
-
If disabled, Sinatra will allow relative redirects, however, Sinatra
will no longer conform with RFC 2616 (HTTP 1.1), which only allows
absolute redirects.
-
Enable if your app is running behind a reverse proxy that has not been
set up properly. Note that the url helper will still produce
absolute URLs, unless you pass in false as the second
parameter.
- Disabled by default.
- add_charset
-
Mime types the content_type helper will automatically add the
charset info to. You should add to it rather than overriding this
option: settings.add_charset << "application/foobar"
- app_file
-
Path to the main application file, used to detect project root, views
and public folder and inline templates.
- bind
-
IP address to bind to (default: 0.0.0.0 or
localhost if your `environment` is set to development). Only
used for built-in server.
- default_content_type
-
Content-Type to assume if unknown (defaults to "text/html"). Set
to nil to not set a default Content-Type on every response; when
configured so, you must set the Content-Type manually when emitting content
or the user-agent will have to sniff it (or, if nosniff is enabled
in Rack::Protection::XSSHeader, assume application/octet-stream).
- default_encoding
- Encoding to assume if unknown (defaults to "utf-8").
- dump_errors
- Display errors in the log. Enabled by default unless environment is "test".
- environment
-
Current environment. Defaults to ENV['APP_ENV'], or
"development" if not available.
- host_authorization
-
You can pass a hash of options to host_authorization,
to be used by the Rack::Protection::HostAuthorization middleware.
The middleware can block requests with unrecognized hostnames, to prevent DNS rebinding
and other host header attacks. It checks the Host, X-Forwarded-Host
and Forwarded headers.
Useful options are:
- permitted_hosts – an array of hostnames (and IPAddr objects) your app recognizes
- in the development environment, it is set to .localhost, .test and any IPv4/IPv6 address
- if empty, any hostname is permitted (the default for any other environment)
- status – the HTTP status code used in the response when a request is blocked (defaults to 403)
- message – the body used in the response when a request is blocked (defaults to Host not permitted)
- allow_if – supply a Proc to use custom allow/deny logic, the proc is passed the request environment
logging
Use the logger.
lock
Places a lock around every request, only running processing on request
per Ruby process concurrently.
Enabled if your app is not thread-safe. Disabled by default.
method_override
Use _method magic to allow put/delete forms in browsers that
don't support it.
mustermann_opts
A default hash of options to pass to Mustermann.new when compiling routing
paths.
port
Port to listen on. Only used for built-in server.
prefixed_redirects
Whether or not to insert request.script_name into redirects
if no absolute path is given. That way redirect '/foo' would
behave like redirect to('/foo'). Disabled by default.
protection
Whether or not to enable web attack protections. See protection section
above.
public_dir
Alias for public_folder. See below.
public_folder
Path to the folder public files are served from. Only used if static
file serving is enabled (see static setting below). Inferred
from app_file setting if not set.
quiet
Disables logs generated by Sinatra's start and stop commands.
false by default.
reload_templates
Whether or not to reload templates between requests. Enabled in
development mode.
root
Path to project root folder. Inferred from app_file setting
if not set.
raise_errors
Raise unhandled errors (will stop application). Enabled by default when
environment is set to "test", disabled otherwise.
Any explicitly defined error handlers always override this setting. See
the "Error" section below.
run
If enabled, Sinatra will handle starting the web server. Do not
enable if using rackup or other means.
running
Is the built-in server running now? Do not change this setting!
server
Server or list of servers to use for built-in server. Order indicates
priority, default depends on Ruby implementation.
server_settings
You can pass a hash of options to server_settings,
such as Host or Port.
sessions
Enable cookie-based sessions support using
Rack::Session::Cookie. See 'Using Sessions' section for more
information.
session_store
The Rack session middleware used. Defaults to
Rack::Session::Cookie. See 'Using Sessions' section for more
information.
show_exceptions
Show a stack trace in the browser when an exception happens. Enabled by
default when environment is set to "development",
disabled otherwise.
Can also be set to :after_handler to trigger app-specified
error handling before showing a stack trace in the browser.
static
Whether Sinatra should handle serving static files.
Disable when using a server able to do this on its own.
Disabling will boost performance.
Enabled by default in classic style, disabled for modular apps.
static_cache_control
When Sinatra is serving static files, set this to add
Cache-Control headers to the responses. Uses the
cache_control helper. Disabled by default.
Use an explicit array when setting multiple values:
set :static_cache_control, [:public, :max_age => 300]
threaded
If set to true, will tell server to use
EventMachine.defer for processing the request.
traps
Whether Sinatra should handle system signals.
views
Path to the views folder. Inferred from app_file setting if
not set.
x_cascade
Whether or not to set the X-Cascade header if no route matches.
Defaults to true.
## Lifecycle Events
There are 2 lifecycle events currently exposed by Sinatra. One when the server starts and one when it stops.
They can be used like this:
```ruby
on_start do
puts "===== Booting up ====="
end
on_stop do
puts "===== Shutting down ====="
end
```
Note that these callbacks only work when using Sinatra to start the web server.
## Environments
There are three predefined `environments`: `"development"`,
`"production"` and `"test"`. Environments can be set through the
`APP_ENV` environment variable. The default value is `"development"`.
In the `"development"` environment all templates are reloaded between
requests, and special `not_found` and `error` handlers display stack
traces in your browser. In the `"production"` and `"test"` environments,
templates are cached by default.
To run different environments, set the `APP_ENV` environment variable:
```shell
APP_ENV=production ruby my_app.rb
```
You can use predefined methods: `development?`, `test?` and `production?` to
check the current environment setting:
```ruby
get '/' do
if settings.development?
"development!"
else
"not development!"
end
end
```
## Error Handling
Error handlers run within the same context as routes and before filters,
which means you get all the goodies it has to offer, like `haml`, `erb`,
`halt`, etc.
### Not Found
When a `Sinatra::NotFound` exception is raised, or the response's status
code is 404, the `not_found` handler is invoked:
```ruby
not_found do
'This is nowhere to be found.'
end
```
### Error
The `error` handler is invoked any time an exception is raised from a route
block or a filter. But note in development it will only run if you set the
show exceptions option to `:after_handler`:
```ruby
set :show_exceptions, :after_handler
```
A catch-all error handler can be defined with `error` and a block:
```ruby
error do
'Sorry there was a nasty error'
end
```
The exception object can be obtained from the `sinatra.error` Rack variable:
```ruby
error do
'Sorry there was a nasty error - ' + env['sinatra.error'].message
end
```
Pass an error class as an argument to create handlers for custom errors:
```ruby
error MyCustomError do
'So what happened was...' + env['sinatra.error'].message
end
```
Then, if this happens:
```ruby
get '/' do
raise MyCustomError, 'something bad'
end
```
You get this:
```
So what happened was... something bad
```
Alternatively, you can install an error handler for a status code:
```ruby
error 403 do
'Access forbidden'
end
get '/secret' do
403
end
```
Or a range:
```ruby
error 400..510 do
'Boom'
end
```
Sinatra installs special `not_found` and `error` handlers when
running under the development environment to display nice stack traces
and additional debugging information in your browser.
### Behavior with `raise_errors` option
When `raise_errors` option is `true`, errors that are unhandled are raised
outside of the application. Additionally, any errors that would have been
caught by the catch-all error handler are raised.
For example, consider the following configuration:
```ruby
# First handler
error MyCustomError do
'A custom message'
end
# Second handler
error do
'A catch-all message'
end
```
If `raise_errors` is `false`:
* When `MyCustomError` or descendant is raised, the first handler is invoked.
The HTTP response body will contain `"A custom message"`.
* When any other error is raised, the second handler is invoked. The HTTP
response body will contain `"A catch-all message"`.
If `raise_errors` is `true`:
* When `MyCustomError` or descendant is raised, the behavior is identical to
when `raise_errors` is `false`, described above.
* When any other error is raised, the second handler is *not* invoked, and
the error is raised outside of the application.
* If the environment is `production`, the HTTP response body will contain
a generic error message, e.g. `"An unhandled lowlevel error occurred. The
application logs may have details."`
* If the environment is not `production`, the HTTP response body will contain
the verbose error backtrace.
* Regardless of environment, if `show_exceptions` is set to `:after_handler`,
the HTTP response body will contain the verbose error backtrace.
In the `test` environment, `raise_errors` is set to `true` by default. This
means that in order to write a test for a catch-all error handler,
`raise_errors` must temporarily be set to `false` for that particular test.
## Rack Middleware
Sinatra rides on [Rack](https://rack.github.io/), a minimal standard
interface for Ruby web frameworks. One of Rack's most interesting
capabilities for application developers is support for "middleware" --
components that sit between the server and your application monitoring
and/or manipulating the HTTP request/response to provide various types
of common functionality.
Sinatra makes building Rack middleware pipelines a cinch via a top-level
`use` method:
```ruby
require 'sinatra'
require 'my_custom_middleware'
use Rack::Lint
use MyCustomMiddleware
get '/hello' do
'Hello World'
end
```
The semantics of `use` are identical to those defined for the
[Rack::Builder](https://www.rubydoc.info/github/rack/rack/main/Rack/Builder) DSL
(most frequently used from rackup files). For example, the `use` method
accepts multiple/variable args as well as blocks:
```ruby
use Rack::Auth::Basic do |username, password|
username == 'admin' && password == 'secret'
end
```
Rack is distributed with a variety of standard middleware for logging,
debugging, URL routing, authentication, and session handling. Sinatra uses
many of these components automatically based on configuration so you
typically don't have to `use` them explicitly.
You can find useful middleware in
[rack](https://github.com/rack/rack/tree/main/lib/rack),
[rack-contrib](https://github.com/rack/rack-contrib#readme),
or in the [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware).
## Testing
Sinatra tests can be written using any Rack-based testing library or
framework.
[Rack::Test](https://www.rubydoc.info/github/rack/rack-test/main/frames)
is recommended:
```ruby
require 'my_sinatra_app'
require 'minitest/autorun'
require 'rack/test'
class MyAppTest < Minitest::Test
include Rack::Test::Methods
def app
Sinatra::Application
end
def test_my_default
get '/'
assert_equal 'Hello World!', last_response.body
end
def test_with_params
get '/meet', :name => 'Frank'
assert_equal 'Hello Frank!', last_response.body
end
def test_with_user_agent
get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
assert_equal "You're using Songbird!", last_response.body
end
end
```
Note: If you are using Sinatra in the modular style, replace
`Sinatra::Application` above with the class name of your app.
## Sinatra::Base - Middleware, Libraries, and Modular Apps
Defining your app at the top-level works well for micro-apps but has
considerable drawbacks when building reusable components such as Rack
middleware, Rails metal, simple libraries with a server component, or even
Sinatra extensions. The top-level assumes a micro-app style configuration
(e.g., a single application file, `./public` and `./views`
directories, logging, exception detail page, etc.). That's where
`Sinatra::Base` comes into play:
```ruby
require 'sinatra/base'
class MyApp < Sinatra::Base
set :sessions, true
set :foo, 'bar'
get '/' do
'Hello world!'
end
end
```
The methods available to `Sinatra::Base` subclasses are exactly the same
as those available via the top-level DSL. Most top-level apps can be
converted to `Sinatra::Base` components with two modifications:
* Your file should require `sinatra/base` instead of `sinatra`;
otherwise, all of Sinatra's DSL methods are imported into the main
namespace.
* Put your app's routes, error handlers, filters, and options in a subclass
of `Sinatra::Base`.
`Sinatra::Base` is a blank slate. Most options are disabled by default,
including the built-in server. See [Configuring
Settings](http://www.sinatrarb.com/configuration.html) for details on
available options and their behavior. If you want behavior more similar
to when you define your app at the top level (also known as Classic
style), you can subclass `Sinatra::Application`:
```ruby
require 'sinatra/base'
class MyApp < Sinatra::Application
get '/' do
'Hello world!'
end
end
```
### Modular vs. Classic Style
Contrary to common belief, there is nothing wrong with the classic
style. If it suits your application, you do not have to switch to a
modular application.
The main disadvantage of using the classic style rather than the modular
style is that you will only have one Sinatra application per Ruby
process. If you plan to use more than one, switch to the modular style.
There is no reason you cannot mix the modular and classic styles.
If switching from one style to the other, you should be aware of
slightly different default settings:
Setting |
Classic |
Modular |
Modular |
app_file |
file loading sinatra |
file subclassing Sinatra::Base |
file subclassing Sinatra::Application |
run |
$0 == app_file |
false |
false |
logging |
true |
false |
true |
method_override |
true |
false |
true |
inline_templates |
true |
false |
true |
static |
true |
File.exist?(public_folder) |
true |
### Serving a Modular Application
There are two common options for starting a modular app, actively
starting with `run!`:
```ruby
# my_app.rb
require 'sinatra/base'
class MyApp < Sinatra::Base
# ... app code here ...
# start the server if ruby file executed directly
run! if app_file == $0
end
```
Start with:
```shell
ruby my_app.rb
```
Or with a `config.ru` file, which allows using any Rack handler:
```ruby
# config.ru (run with rackup)
require './my_app'
run MyApp
```
Run:
```shell
rackup -p 4567
```
### Using a Classic Style Application with a config.ru
Write your app file:
```ruby
# app.rb
require 'sinatra'
get '/' do
'Hello world!'
end
```
And a corresponding `config.ru`:
```ruby
require './app'
run Sinatra::Application
```
### When to use a config.ru?
A `config.ru` file is recommended if:
* You want to deploy with a different Rack handler (Passenger, Unicorn,
Heroku, ...).
* You want to use more than one subclass of `Sinatra::Base`.
* You want to use Sinatra only for middleware, and not as an endpoint.
**There is no need to switch to a `config.ru` simply because you
switched to the modular style, and you don't have to use the modular
style for running with a `config.ru`.**
### Using Sinatra as Middleware
Not only is Sinatra able to use other Rack middleware, any Sinatra
application can, in turn, be added in front of any Rack endpoint as
middleware itself. This endpoint could be another Sinatra application,
or any other Rack-based application (Rails/Hanami/Roda/...):
```ruby
require 'sinatra/base'
class LoginScreen < Sinatra::Base
enable :sessions
get('/login') { haml :login }
post('/login') do
if params['name'] == 'admin' && params['password'] == 'admin'
session['user_name'] = params['name']
else
redirect '/login'
end
end
end
class MyApp < Sinatra::Base
# middleware will run before filters
use LoginScreen
before do
unless session['user_name']
halt "Access denied, please login."
end
end
get('/') { "Hello #{session['user_name']}." }
end
```
### Dynamic Application Creation
Sometimes you want to create new applications at runtime without having to
assign them to a constant. You can do this with `Sinatra.new`:
```ruby
require 'sinatra/base'
my_app = Sinatra.new { get('/') { "hi" } }
my_app.run!
```
It takes the application to inherit from as an optional argument:
```ruby
# config.ru (run with rackup)
require 'sinatra/base'
controller = Sinatra.new do
enable :logging
helpers MyHelpers
end
map('/a') do
run Sinatra.new(controller) { get('/') { 'a' } }
end
map('/b') do
run Sinatra.new(controller) { get('/') { 'b' } }
end
```
This is especially useful for testing Sinatra extensions or using Sinatra in
your own library.
This also makes using Sinatra as middleware extremely easy:
```ruby
require 'sinatra/base'
use Sinatra do
get('/') { ... }
end
run RailsProject::Application
```
## Scopes and Binding
The scope you are currently in determines what methods and variables are
available.
### Application/Class Scope
Every Sinatra application corresponds to a subclass of `Sinatra::Base`.
If you are using the top-level DSL (`require 'sinatra'`), then this
class is `Sinatra::Application`, otherwise it is the subclass you
created explicitly. At the class level, you have methods like `get` or
`before`, but you cannot access the `request` or `session` objects, as
there is only a single application class for all requests.
Options created via `set` are methods at class level:
```ruby
class MyApp < Sinatra::Base
# Hey, I'm in the application scope!
set :foo, 42
foo # => 42
get '/foo' do
# Hey, I'm no longer in the application scope!
end
end
```
You have the application scope binding inside:
* Your application class body
* Methods defined by extensions
* The block passed to `helpers`
* Procs/blocks used as a value for `set`
* The block passed to `Sinatra.new`
You can reach the scope object (the class) like this:
* Via the object passed to configure blocks (`configure { |c| ... }`)
* `settings` from within the request scope
### Request/Instance Scope
For every incoming request, a new instance of your application class is
created, and all handler blocks run in that scope. From within this scope you
can access the `request` and `session` objects or call rendering methods like
`erb` or `haml`. You can access the application scope from within the request
scope via the `settings` helper:
```ruby
class MyApp < Sinatra::Base
# Hey, I'm in the application scope!
get '/define_route/:name' do
# Request scope for '/define_route/:name'
@value = 42
settings.get("/#{params['name']}") do
# Request scope for "/#{params['name']}"
@value # => nil (not the same request)
end
"Route defined!"
end
end
```
You have the request scope binding inside:
* get, head, post, put, delete, options, patch, link and unlink blocks
* before and after filters
* helper methods
* templates/views
### Delegation Scope
The delegation scope just forwards methods to the class scope. However, it
does not behave exactly like the class scope, as you do not have the class
binding. Only methods explicitly marked for delegation are available, and you
do not share variables/state with the class scope (read: you have a different
`self`). You can explicitly add method delegations by calling
`Sinatra::Delegator.delegate :method_name`.
You have the delegate scope binding inside:
* The top-level binding, if you did `require "sinatra"`
* An object extended with the `Sinatra::Delegator` mixin
Have a look at the code for yourself: here's the
[Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633)
being [extending the main object](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30).
## Command Line
Sinatra applications can be run directly:
```shell
ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
```
Options are:
```
-h # help
-p # set the port (default is 4567)
-o # set the host (default is 0.0.0.0)
-e # set the environment (default is development)
-s # specify rack server/handler (default is puma)
-q # turn on quiet mode for server (default is off)
-x # turn on the mutex lock (default is off)
```
### Multi-threading
_Paraphrasing from
[this StackOverflow answer](https://stackoverflow.com/a/6282999/5245129)
by Konstantin_
Sinatra doesn't impose any concurrency model but leaves that to the
underlying Rack handler (server) like Puma or Falcon. Sinatra
itself is thread-safe, so there won't be any problem if the Rack handler
uses a threaded model of concurrency.
## Requirement
The following Ruby versions are officially supported:
- Ruby
-
The stable releases are fully supported and recommended.
- TruffleRuby
-
The latest stable release of TruffleRuby is supported.
- JRuby
-
The latest stable release of JRuby is supported. It is not
recommended to use C extensions with JRuby.
Versions of Ruby before 2.7.8 are no longer supported as of Sinatra 4.0.0.
Sinatra should work on any operating system supported by the chosen Ruby
implementation.
Running Sinatra on a not officially supported Ruby flavor means that if things only break there we assume it's not our issue but theirs.
## The Bleeding Edge
If you would like to use Sinatra's latest bleeding-edge code, feel free
to run your application against the main branch, it should be rather
stable.
We also push out prerelease gems from time to time, so you can do a
```shell
gem install sinatra --pre
```
to get some of the latest features.
### With Bundler
If you want to run your application with the latest Sinatra, using
[Bundler](https://bundler.io) is the recommended way.
First, install bundler, if you haven't:
```shell
gem install bundler
```
Then, in your project directory, create a `Gemfile`:
```ruby
source 'https://rubygems.org'
gem 'sinatra', :github => 'sinatra/sinatra'
# other dependencies
gem 'haml' # for instance, if you use haml
```
Note that you will have to list all your application's dependencies in
the `Gemfile`. Sinatra's direct dependencies (Rack and Tilt) will,
however, be automatically fetched and added by Bundler.
Now you can run your app like this:
```shell
bundle exec ruby myapp.rb
```
## Versioning
Sinatra follows [Semantic Versioning](https://semver.org/), both SemVer and
SemVerTag.
## Further Reading
* [Project Website](https://sinatrarb.com/) - Additional documentation,
news, and links to other resources.
* [Contributing](https://sinatrarb.com/contributing) - Find a bug? Need
help? Have a patch?
* [Issue tracker](https://github.com/sinatra/sinatra/issues)
* [Twitter](https://twitter.com/sinatra)
* [Mailing List](https://groups.google.com/forum/#!forum/sinatrarb)
* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on [Freenode](https://freenode.net)
* [Sinatra & Friends](https://discord.gg/ncjsfsNHh7) on Discord
* [Sinatra Book](https://github.com/sinatra/sinatra-book) - Cookbook Tutorial
* [Sinatra Recipes](http://recipes.sinatrarb.com/) - Community contributed
recipes
* API documentation for the [latest release](https://www.rubydoc.info/gems/sinatra)
or the [current HEAD](https://www.rubydoc.info/github/sinatra/sinatra) on
[RubyDoc](https://www.rubydoc.info/)
* [CI Actions](https://github.com/sinatra/sinatra/actions)
sinatra-4.1.1/RELEASING.md 0000664 0000000 0000000 00000003255 14717402212 0015016 0 ustar 00root root 0000000 0000000 # Releasing Sinatra 🥂
This document explains releasing process for all Sinatra gems.
Since everything is bundled in same repo (except `Mustermann`), we
have some rake tasks and a GitHub Actions workflow to cut a release.
(Please refer to [Mustermann](https://github.com/sinatra/mustermann) if that also needs a release.)
### Releasing
For releasing new version of [`sinatra`, `sinatra-contrib`, `rack-protection`], this is the procedure:
1. Update `CHANGELOG.md`
1. Update `VERSION` file with target version
1. Run `rake release:commit_version`
1. Create pull request with all that ([example](https://github.com/sinatra/sinatra/pull/1893))
1. Merge the pull request when CI is green
1. Ensure you have latest changes locally
1. Run `rake release:tag_version`
1. Push tag to upstream
1. Run `rake release:watch` and watch GitHub Actions push to RubyGems.org
### Packaging
These rake tasks will generate `.gem` and `.tar.gz` files. For each gem,
there is one dedicated rake task.
```sh
# Build sinatra-contrib package
$ bundle exec rake package:sinatra-contrib
# Build rack-protection package
$ bundle exec rake package:rack-protection
# Build sinatra package
$ bundle exec rake package:sinatra
# Build all packages
$ bundle exec rake package:all
```
### Packaging and installing locally
These rake tasks will package all the gems, and install them locally
```sh
# Build and install sinatra-contrib gem locally
$ bundle exec rake install:sinatra-contrib
# Build and install rack-protection gem locally
$ bundle exec rake install:rack-protection
# Build and install sinatra gem locally
$ bundle exec rake install:sinatra
# Build and install all gems locally
$ bundle exec rake install:all
```
sinatra-4.1.1/Rakefile 0000664 0000000 0000000 00000015542 14717402212 0014632 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'rake/clean'
require 'minitest/test_task'
require 'fileutils'
require 'date'
task default: :test
def source_version
@source_version ||= File.read(File.expand_path('VERSION', __dir__)).strip
end
def prev_feature
source_version.gsub(/^(\d\.)(\d+)\..*$/) { $1 + ($2.to_i - 1).to_s }
end
def prev_version
return "#{prev_feature}.0" if source_version.end_with? '.0'
source_version.gsub(/\d+$/) { |s| s.to_i - 1 }
end
# Tests ===============================================================
Minitest::TestTask.create # Default `test` task
Minitest::TestTask.create(:'test:core') do |t|
t.warning = true
t.test_globs = %w[
base delegator encoding extensions filter
helpers mapped_error middleware rdoc
readme request response result route_added_hook
routing server settings sinatra static templates
].map { |n| "test/#{n}_test.rb" }
end
# Test code coverage ==================================================
namespace :test do
desc 'Measures test coverage'
task :coverage do
rm_f 'coverage'
ENV['COVERAGE'] = '1'
Rake::Task['test'].invoke
end
end
CLEAN.include('coverage')
# Website =============================================================
desc 'Generate RDoc under doc/api'
task 'doc' => ['doc:api']
task('doc:api') { sh 'yardoc -o doc/api' }
CLEAN.include 'doc/api'
# README ===============================================================
task :add_template, [:name] do |_t, args|
Dir.glob('README.*') do |file|
code = File.read(file)
if code =~ /^===.*#{args.name.capitalize}/
puts "Already covered in #{file}"
else
template = code[%r{===[^\n]*Liquid.*index\.liquid[^\n]*}m]
if template
puts "Adding section to #{file}"
template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase)
code.gsub! /^(\s*===.*CoffeeScript)/, "\n" << template << "\n\\1"
File.open(file, 'w') { |f| f << code }
else
puts "Liquid not found in #{file}"
end
end
end
end
# Thanks in announcement ===============================================
team = ['Ryan Tomayko', 'Blake Mizerany', 'Simon Rozet', 'Konstantin Haase', 'Zachary Scott']
desc 'list of contributors'
task :thanks, ['release:all', :backports] do |_t, a|
a.with_defaults release: "#{prev_version}..HEAD",
backports: "#{prev_feature}.0..#{prev_feature}.x"
included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.map { |l| l.force_encoding('binary') }
excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.map { |l| l.force_encoding('binary') }
commits = (included - excluded).group_by { |c| c[/^[^\t]+/] }
authors = commits.keys.sort_by { |n| - commits[n].size } - team
puts authors[0..-2].join(', ') << ' and ' << authors.last,
"(based on commits included in #{a.release}, but not in #{a.backports})"
end
desc 'list of authors'
task :authors, [:commit_range, :format, :sep] do |_t, a|
a.with_defaults format: '%s (%d)', sep: ', ', commit_range: '--all'
authors = Hash.new(0)
blake = 'Blake Mizerany'
overall = 0
mapping = {
'blake.mizerany@gmail.com' => blake, 'bmizerany' => blake,
'a_user@mac.com' => blake, 'ichverstehe' => 'Harry Vangberg',
'Wu Jiang (nouse)' => 'Wu Jiang'
}
`git shortlog -s #{a.commit_range}`.lines.map do |line|
line = line.force_encoding 'binary' if line.respond_to? :force_encoding
num, name = line.split("\t", 2).map(&:strip)
authors[mapping[name] || name] += num.to_i
overall += num.to_i
end
puts "#{overall} commits by #{authors.count} authors:"
puts authors.sort_by { |_n, c| -c }.map { |e| a.format % e }.join(a.sep)
end
desc 'generates TOC'
task :toc, [:readme] do |_t, a|
a.with_defaults readme: 'README.md'
def self.link(title)
title.downcase.gsub(/(?!-)\W /, '-').gsub(' ', '-').gsub(/(?!-)\W/, '')
end
puts '* [Sinatra](#sinatra)'
title = Regexp.new('(?<=\* )(.*)') # so Ruby 1.8 doesn't complain
File.binread(a.readme).scan(/^##.*/) do |line|
puts line.gsub(/#(?=#)/, ' ').gsub('#', '*').gsub(title) { "[#{$1}](##{link($1)})" }
end
end
# PACKAGING ============================================================
if defined?(Gem)
GEMS_AND_ROOT_DIRECTORIES = {
'sinatra' => '.',
'sinatra-contrib' => './sinatra-contrib',
'rack-protection' => './rack-protection'
}.freeze
def package(gem, ext = '')
"pkg/#{gem}-#{source_version}" + ext
end
directory 'pkg/'
CLOBBER.include('pkg')
GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory|
file package(gem, '.gem') => ['pkg/', "#{"#{directory}/#{gem}"}.gemspec"] do |f|
sh "cd #{directory} && gem build #{gem}.gemspec"
mv "#{directory}/#{File.basename(f.name)}", f.name
end
file package(gem, '.tar.gz') => ['pkg/'] do |f|
sh <<-SH
git archive \
--prefix=#{gem}-#{source_version}/ \
--format=tar \
HEAD -- #{directory} | gzip > #{f.name}
SH
end
end
namespace :package do
GEMS_AND_ROOT_DIRECTORIES.each do |gem, _directory|
desc "Build #{gem} packages"
task gem => %w[.gem .tar.gz].map { |e| package(gem, e) }
end
desc 'Build all packages'
task all: GEMS_AND_ROOT_DIRECTORIES.keys
end
namespace :install do
GEMS_AND_ROOT_DIRECTORIES.each do |gem, _directory|
desc "Build and install #{gem} as local gem"
task gem => package(gem, '.gem') do
sh "gem install #{package(gem, '.gem')}"
end
end
desc 'Build and install all of the gems as local gems'
task all: GEMS_AND_ROOT_DIRECTORIES.keys
end
namespace :release do
GEMS_AND_ROOT_DIRECTORIES.each do |gem, _directory|
desc "Release #{gem} as a package"
task gem => "package:#{gem}" do
sh <<-SH
gem install #{package(gem, '.gem')} --local &&
gem push #{package(gem, '.gem')}
SH
end
end
desc 'Commits the version to git (no push)'
task :commit_version do
%w[
lib/sinatra
sinatra-contrib/lib/sinatra/contrib
rack-protection/lib/rack/protection
].each do |path|
path = File.join(path, 'version.rb')
File.write(path, File.read(path).sub(/VERSION = '(.+?)'/, "VERSION = '#{source_version}'"))
end
sh <<-SH
git commit --allow-empty --all --message '#{source_version} release'
SH
end
desc 'Tags the version in git (no push)'
task :tag_version do
sh <<-SH
git tag --sign v#{source_version} --message '#{source_version} release'
SH
end
desc 'Watch the release workflow run'
task :watch do
sh <<-SH
runId=$(gh run list --workflow=release.yml --limit 1 --json databaseId --jq '.[].databaseId')
gh run watch --interval 1 --exit-status $runId
SH
end
desc 'Release all gems as packages'
task all: %i[test commit_version] + GEMS_AND_ROOT_DIRECTORIES.keys
end
end
sinatra-4.1.1/SECURITY.md 0000664 0000000 0000000 00000005443 14717402212 0014755 0 ustar 00root root 0000000 0000000 # Reporting a security bug
All security bugs in Sinatra should be reported to the core team through our private mailing list [sinatra-security@googlegroups.com](https://groups.google.com/group/sinatra-security). Your report will be acknowledged within 24 hours, and you’ll receive a more detailed response to your email within 48 hours indicating the next steps in handling your report.
After the initial reply to your report the security team will endeavor to keep you informed of the progress being made towards a fix and full announcement. These updates will be sent at least every five days, in reality this is more likely to be every 24-48 hours.
If you have not received a reply to your email within 48 hours, or have not heard from the security team for the past five days there are a few steps you can take:
* Reach out to us on [discord](https://discord.gg/ncjsfsNHh7)
## Disclosure Policy
Sinatra has a 5 step disclosure policy, that is upheld to the best of our ability.
1. Security report received and is assigned a primary handler. This person will coordinate the fix and release process.
2. Problem is confirmed and, a list of all affected versions is determined. Code is audited to find any potential similar problems.
3. Fixes are prepared for all releases which are still supported. These fixes are not committed to the public repository but rather held locally pending the announcement.
4. A suggested embargo date for this vulnerability is chosen and distros@openwall is notified. This notification will include patches for all versions still under support and a contact address for packagers who need advice back-porting patches to older versions.
5. On the embargo date, the [mailing list][mailing-list] and [security list][security-list] are sent a copy of the announcement. The changes are pushed to the public repository and new gems released to rubygems.
Typically the embargo date will be set 72 hours from the time vendor-sec is first notified, however this may vary depending on the severity of the bug or difficulty in applying a fix.
This process can take some time, especially when coordination is required with maintainers of other projects. Every effort will be made to handle the bug in as timely a manner as possible, however it’s important that we follow the release process above to ensure that the disclosure is handled in a consistent manner.
## Security Updates
Security updates will be posted on the [mailing list][mailing-list] and [security list][security-list].
## Comments on this Policy
If you have any suggestions to improve this policy, please send an email the core team at [sinatrarb@googlegroups.com](https://groups.google.com/group/sinatrarb).
[mailing-list]: http://groups.google.com/group/sinatrarb/topics
[security-list]: http://groups.google.com/group/sinatra-security/topics
sinatra-4.1.1/VERSION 0000664 0000000 0000000 00000000006 14717402212 0014222 0 ustar 00root root 0000000 0000000 4.1.1
sinatra-4.1.1/examples/ 0000775 0000000 0000000 00000000000 14717402212 0014774 5 ustar 00root root 0000000 0000000 sinatra-4.1.1/examples/chat.rb 0000775 0000000 0000000 00000003141 14717402212 0016242 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby -I ../lib -I lib
# frozen_string_literal: true
# This example does *not* work properly with WEBrick or other
# servers that buffer output. To shut down the server, close any
# open browser tabs that are connected to the chat server.
require 'sinatra'
set :server, :puma
connections = Set.new
get '/' do
halt erb(:login) unless params[:user]
erb :chat, locals: { user: params[:user].gsub(/\W/, '') }
end
get '/stream', provides: 'text/event-stream' do
stream :keep_open do |out|
if connections.add?(out)
out.callback { connections.delete(out) }
end
out << "heartbeat:\n"
sleep 1
rescue
out.close
end
end
post '/' do
connections.each do |out|
out << "data: #{params[:msg]}\n\n"
rescue
out.close
end
204 # response without entity body
end
__END__
@@ layout
Super Simple Chat with Sinatra
<%= yield %>
@@ login
@@ chat
sinatra-4.1.1/examples/lifecycle_events.rb 0000664 0000000 0000000 00000000506 14717402212 0020645 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby -I ../lib -I lib
# frozen_string_literal: true
require 'sinatra'
get('/') do
'This shows how lifecycle events work'
end
on_start do
puts "=============="
puts " Booting up"
puts "=============="
end
on_stop do
puts "================="
puts " Shutting down"
puts "================="
end
sinatra-4.1.1/examples/simple.rb 0000775 0000000 0000000 00000000172 14717402212 0016615 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby -I ../lib -I lib
# frozen_string_literal: true
require 'sinatra'
get('/') { 'this is a simple app' }
sinatra-4.1.1/examples/stream.ru 0000664 0000000 0000000 00000000762 14717402212 0016644 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
# this example does *not* work properly with WEBrick
#
# run *one* of these:
#
# unicorn stream.ru # gem install unicorn
# puma stream.ru # gem install puma
require 'sinatra/base'
class Stream < Sinatra::Base
get '/' do
content_type :txt
stream do |out|
out << "It's gonna be legen -\n"
sleep 0.5
out << " (wait for it) \n"
sleep 1
out << "- dary!\n"
end
end
end
run Stream
sinatra-4.1.1/lib/ 0000775 0000000 0000000 00000000000 14717402212 0013724 5 ustar 00root root 0000000 0000000 sinatra-4.1.1/lib/sinatra.rb 0000664 0000000 0000000 00000000120 14717402212 0015703 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'sinatra/main'
enable :inline_templates
sinatra-4.1.1/lib/sinatra/ 0000775 0000000 0000000 00000000000 14717402212 0015365 5 ustar 00root root 0000000 0000000 sinatra-4.1.1/lib/sinatra/base.rb 0000664 0000000 0000000 00000205307 14717402212 0016633 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
# external dependencies
require 'rack'
begin
require 'rackup'
rescue LoadError
end
require 'tilt'
require 'rack/protection'
require 'rack/session'
require 'mustermann'
require 'mustermann/sinatra'
require 'mustermann/regular'
# stdlib dependencies
require 'ipaddr'
require 'time'
require 'uri'
# other files we need
require 'sinatra/indifferent_hash'
require 'sinatra/show_exceptions'
require 'sinatra/version'
require_relative 'middleware/logger'
module Sinatra
# The request object. See Rack::Request for more info:
# https://rubydoc.info/github/rack/rack/main/Rack/Request
class Request < Rack::Request
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/.freeze
HEADER_VALUE_WITH_PARAMS = %r{(?:(?:\w+|\*)/(?:\w+(?:\.|-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*}.freeze
# Returns an array of acceptable media types for the response
def accept
@env['sinatra.accept'] ||= if @env.include?('HTTP_ACCEPT') && (@env['HTTP_ACCEPT'].to_s != '')
@env['HTTP_ACCEPT']
.to_s
.scan(HEADER_VALUE_WITH_PARAMS)
.map! { |e| AcceptEntry.new(e) }
.sort
else
[AcceptEntry.new('*/*')]
end
end
def accept?(type)
preferred_type(type).to_s.include?(type)
end
def preferred_type(*types)
return accept.first if types.empty?
types.flatten!
return types.first if accept.empty?
accept.detect do |accept_header|
type = types.detect { |t| MimeTypeEntry.new(t).accepts?(accept_header) }
return type if type
end
end
alias secure? ssl?
def forwarded?
!forwarded_authority.nil?
end
def safe?
get? || head? || options? || trace?
end
def idempotent?
safe? || put? || delete? || link? || unlink?
end
def link?
request_method == 'LINK'
end
def unlink?
request_method == 'UNLINK'
end
def params
super
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise BadRequest, "Invalid query parameters: #{Rack::Utils.escape_html(e.message)}"
rescue EOFError => e
raise BadRequest, "Invalid multipart/form-data: #{Rack::Utils.escape_html(e.message)}"
end
class AcceptEntry
attr_accessor :params
attr_reader :entry
def initialize(entry)
params = entry.scan(HEADER_PARAM).map! do |s|
key, value = s.strip.split('=', 2)
value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"')
[key, value]
end
@entry = entry
@type = entry[/[^;]+/].delete(' ')
@params = params.to_h
@q = @params.delete('q') { 1.0 }.to_f
end
def <=>(other)
other.priority <=> priority
end
def priority
# We sort in descending order; better matches should be higher.
[@q, -@type.count('*'), @params.size]
end
def to_str
@type
end
def to_s(full = false)
full ? entry : to_str
end
def respond_to?(*args)
super || to_str.respond_to?(*args)
end
def method_missing(*args, &block)
to_str.send(*args, &block)
end
end
class MimeTypeEntry
attr_reader :params
def initialize(entry)
params = entry.scan(HEADER_PARAM).map! do |s|
key, value = s.strip.split('=', 2)
value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"')
[key, value]
end
@type = entry[/[^;]+/].delete(' ')
@params = params.to_h
end
def accepts?(entry)
File.fnmatch(entry, self) && matches_params?(entry.params)
end
def to_str
@type
end
def matches_params?(params)
return true if @params.empty?
params.all? { |k, v| !@params.key?(k) || @params[k] == v }
end
end
end
# The response object. See Rack::Response and Rack::Response::Helpers for
# more info:
# https://rubydoc.info/github/rack/rack/main/Rack/Response
# https://rubydoc.info/github/rack/rack/main/Rack/Response/Helpers
class Response < Rack::Response
DROP_BODY_RESPONSES = [204, 304].freeze
def body=(value)
value = value.body while Rack::Response === value
@body = String === value ? [value.to_str] : value
end
def each
block_given? ? super : enum_for(:each)
end
def finish
result = body
if drop_content_info?
headers.delete 'content-length'
headers.delete 'content-type'
end
if drop_body?
close
result = []
end
if calculate_content_length?
# if some other code has already set content-length, don't muck with it
# currently, this would be the static file-handler
headers['content-length'] = body.map(&:bytesize).reduce(0, :+).to_s
end
[status, headers, result]
end
private
def calculate_content_length?
headers['content-type'] && !headers['content-length'] && (Array === body)
end
def drop_content_info?
informational? || drop_body?
end
def drop_body?
DROP_BODY_RESPONSES.include?(status)
end
end
# Some Rack handlers implement an extended body object protocol, however,
# some middleware (namely Rack::Lint) will break it by not mirroring the methods in question.
# This middleware will detect an extended body object and will make sure it reaches the
# handler directly. We do this here, so our middleware and middleware set up by the app will
# still be able to run.
class ExtendedRack < Struct.new(:app)
def call(env)
result = app.call(env)
callback = env['async.callback']
return result unless callback && async?(*result)
after_response { callback.call result }
setup_close(env, *result)
throw :async
end
private
def setup_close(env, _status, _headers, body)
return unless body.respond_to?(:close) && env.include?('async.close')
env['async.close'].callback { body.close }
env['async.close'].errback { body.close }
end
def after_response(&block)
raise NotImplementedError, 'only supports EventMachine at the moment' unless defined? EventMachine
EventMachine.next_tick(&block)
end
def async?(status, _headers, body)
return true if status == -1
body.respond_to?(:callback) && body.respond_to?(:errback)
end
end
# Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing,
# if another CommonLogger is already in the middleware chain.
class CommonLogger < Rack::CommonLogger
def call(env)
env['sinatra.commonlogger'] ? @app.call(env) : super
end
superclass.class_eval do
alias_method :call_without_check, :call unless method_defined? :call_without_check
def call(env)
env['sinatra.commonlogger'] = true
call_without_check(env)
end
end
end
class Error < StandardError # :nodoc:
end
class BadRequest < Error # :nodoc:
def http_status; 400 end
end
class NotFound < Error # :nodoc:
def http_status; 404 end
end
# Methods available to routes, before/after filters, and views.
module Helpers
# Set or retrieve the response status code.
def status(value = nil)
response.status = Rack::Utils.status_code(value) if value
response.status
end
# Set or retrieve the response body. When a block is given,
# evaluation is deferred until the body is read with #each.
def body(value = nil, &block)
if block_given?
def block.each; yield(call) end
response.body = block
elsif value
unless request.head? || value.is_a?(Rack::Files::BaseIterator) || value.is_a?(Stream)
headers.delete 'content-length'
end
response.body = value
else
response.body
end
end
# Halt processing and redirect to the URI provided.
def redirect(uri, *args)
# SERVER_PROTOCOL is required in Rack 3, fall back to HTTP_VERSION
# for servers not updated for Rack 3 (like Puma 5)
http_version = env['SERVER_PROTOCOL'] || env['HTTP_VERSION']
if (http_version == 'HTTP/1.1') && (env['REQUEST_METHOD'] != 'GET')
status 303
else
status 302
end
# According to RFC 2616 section 14.30, "the field value consists of a
# single absolute URI"
response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?)
halt(*args)
end
# Generates the absolute URI for a given path in the app.
# Takes Rack routers and reverse proxies into account.
def uri(addr = nil, absolute = true, add_script_name = true)
return addr if addr.to_s =~ /\A[a-z][a-z0-9+.\-]*:/i
uri = [host = String.new]
if absolute
host << "http#{'s' if request.secure?}://"
host << if request.forwarded? || (request.port != (request.secure? ? 443 : 80))
request.host_with_port
else
request.host
end
end
uri << request.script_name.to_s if add_script_name
uri << (addr || request.path_info).to_s
File.join uri
end
alias url uri
alias to uri
# Halt processing and return the error status provided.
def error(code, body = nil)
if code.respond_to? :to_str
body = code.to_str
code = 500
end
response.body = body unless body.nil?
halt code
end
# Halt processing and return a 404 Not Found.
def not_found(body = nil)
error 404, body
end
# Set multiple response headers with Hash.
def headers(hash = nil)
response.headers.merge! hash if hash
response.headers
end
# Access the underlying Rack session.
def session
request.session
end
# Access shared logger object.
def logger
request.logger
end
# Look up a media type by file extension in Rack's mime registry.
def mime_type(type)
Base.mime_type(type)
end
# Set the content-type of the response body given a media type or file
# extension.
def content_type(type = nil, params = {})
return response['content-type'] unless type
default = params.delete :default
mime_type = mime_type(type) || default
raise format('Unknown media type: %p', type) if mime_type.nil?
mime_type = mime_type.dup
unless params.include?(:charset) || settings.add_charset.all? { |p| !(p === mime_type) }
params[:charset] = params.delete('charset') || settings.default_encoding
end
params.delete :charset if mime_type.include? 'charset'
unless params.empty?
mime_type << (mime_type.include?(';') ? ', ' : ';')
mime_type << params.map do |key, val|
val = val.inspect if val =~ /[";,]/
"#{key}=#{val}"
end.join(', ')
end
response['content-type'] = mime_type
end
# https://html.spec.whatwg.org/#multipart-form-data
MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
'"' => '%22',
"\r" => '%0D',
"\n" => '%0A'
}.freeze
# Set the Content-Disposition to "attachment" with the specified filename,
# instructing the user agents to prompt to save.
def attachment(filename = nil, disposition = :attachment)
response['Content-Disposition'] = disposition.to_s.dup
return unless filename
params = format('; filename="%s"', File.basename(filename).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE))
response['Content-Disposition'] << params
ext = File.extname(filename)
content_type(ext) unless response['content-type'] || ext.empty?
end
# Use the contents of the file at +path+ as the response body.
def send_file(path, opts = {})
if opts[:type] || !response['content-type']
content_type opts[:type] || File.extname(path), default: 'application/octet-stream'
end
disposition = opts[:disposition]
filename = opts[:filename]
disposition = :attachment if disposition.nil? && filename
filename = path if filename.nil?
attachment(filename, disposition) if disposition
last_modified opts[:last_modified] if opts[:last_modified]
file = Rack::Files.new(File.dirname(settings.app_file))
result = file.serving(request, path)
result[1].each { |k, v| headers[k] ||= v }
headers['content-length'] = result[1]['content-length']
opts[:status] &&= Integer(opts[:status])
halt (opts[:status] || result[0]), result[2]
rescue Errno::ENOENT
not_found
end
# Class of the response body in case you use #stream.
#
# Three things really matter: The front and back block (back being the
# block generating content, front the one sending it to the client) and
# the scheduler, integrating with whatever concurrency feature the Rack
# handler is using.
#
# Scheduler has to respond to defer and schedule.
class Stream
def self.schedule(*) yield end
def self.defer(*) yield end
def initialize(scheduler = self.class, keep_open = false, &back)
@back = back.to_proc
@scheduler = scheduler
@keep_open = keep_open
@callbacks = []
@closed = false
end
def close
return if closed?
@closed = true
@scheduler.schedule { @callbacks.each { |c| c.call } }
end
def each(&front)
@front = front
@scheduler.defer do
begin
@back.call(self)
rescue Exception => e
@scheduler.schedule { raise e }
ensure
close unless @keep_open
end
end
end
def <<(data)
@scheduler.schedule { @front.call(data.to_s) }
self
end
def callback(&block)
return yield if closed?
@callbacks << block
end
alias errback callback
def closed?
@closed
end
end
# Allows to start sending data to the client even though later parts of
# the response body have not yet been generated.
#
# The close parameter specifies whether Stream#close should be called
# after the block has been executed.
def stream(keep_open = false)
scheduler = env['async.callback'] ? EventMachine : Stream
current = @params.dup
stream = if scheduler == Stream && keep_open
Stream.new(scheduler, false) do |out|
until out.closed?
with_params(current) { yield(out) }
end
end
else
Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } }
end
body stream
end
# Specify response freshness policy for HTTP caches (Cache-Control header).
# Any number of non-value directives (:public, :private, :no_cache,
# :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
# a Hash of value directives (:max_age, :s_maxage).
#
# cache_control :public, :must_revalidate, :max_age => 60
# => Cache-Control: public, must-revalidate, max-age=60
#
# See RFC 2616 / 14.9 for more on standard cache control directives:
# http://tools.ietf.org/html/rfc2616#section-14.9.1
def cache_control(*values)
if values.last.is_a?(Hash)
hash = values.pop
hash.reject! { |_k, v| v == false }
hash.reject! { |k, v| values << k if v == true }
else
hash = {}
end
values.map! { |value| value.to_s.tr('_', '-') }
hash.each do |key, value|
key = key.to_s.tr('_', '-')
value = value.to_i if %w[max-age s-maxage].include? key
values << "#{key}=#{value}"
end
response['Cache-Control'] = values.join(', ') if values.any?
end
# Set the Expires header and Cache-Control/max-age directive. Amount
# can be an integer number of seconds in the future or a Time object
# indicating when the response should be considered "stale". The remaining
# "values" arguments are passed to the #cache_control helper:
#
# expires 500, :public, :must_revalidate
# => Cache-Control: public, must-revalidate, max-age=500
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
#
def expires(amount, *values)
values << {} unless values.last.is_a?(Hash)
if amount.is_a? Integer
time = Time.now + amount.to_i
max_age = amount
else
time = time_for amount
max_age = time - Time.now
end
values.last.merge!(max_age: max_age) { |_key, v1, v2| v1 || v2 }
cache_control(*values)
response['Expires'] = time.httpdate
end
# Set the last modified time of the resource (HTTP 'Last-Modified' header)
# and halt if conditional GET matches. The +time+ argument is a Time,
# DateTime, or other object that responds to +to_time+.
#
# When the current request includes an 'If-Modified-Since' header that is
# equal or later than the time specified, execution is immediately halted
# with a '304 Not Modified' response.
def last_modified(time)
return unless time
time = time_for time
response['Last-Modified'] = time.httpdate
return if env['HTTP_IF_NONE_MATCH']
if (status == 200) && env['HTTP_IF_MODIFIED_SINCE']
# compare based on seconds since epoch
since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
halt 304 if since >= time.to_i
end
if (success? || (status == 412)) && env['HTTP_IF_UNMODIFIED_SINCE']
# compare based on seconds since epoch
since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
halt 412 if since < time.to_i
end
rescue ArgumentError
end
ETAG_KINDS = %i[strong weak].freeze
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
# GET matches. The +value+ argument is an identifier that uniquely
# identifies the current version of the resource. The +kind+ argument
# indicates whether the etag should be used as a :strong (default) or :weak
# cache validator.
#
# When the current request includes an 'If-None-Match' header with a
# matching etag, execution is immediately halted. If the request method is
# GET or HEAD, a '304 Not Modified' response is sent.
def etag(value, options = {})
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
options = { kind: options } unless Hash === options
kind = options[:kind] || :strong
new_resource = options.fetch(:new_resource) { request.post? }
unless ETAG_KINDS.include?(kind)
raise ArgumentError, ':strong or :weak expected'
end
value = format('"%s"', value)
value = "W/#{value}" if kind == :weak
response['ETag'] = value
return unless success? || status == 304
if etag_matches?(env['HTTP_IF_NONE_MATCH'], new_resource)
halt(request.safe? ? 304 : 412)
end
if env['HTTP_IF_MATCH']
return if etag_matches?(env['HTTP_IF_MATCH'], new_resource)
halt 412
end
nil
end
# Sugar for redirect (example: redirect back)
def back
request.referer
end
# whether or not the status is set to 1xx
def informational?
status.between? 100, 199
end
# whether or not the status is set to 2xx
def success?
status.between? 200, 299
end
# whether or not the status is set to 3xx
def redirect?
status.between? 300, 399
end
# whether or not the status is set to 4xx
def client_error?
status.between? 400, 499
end
# whether or not the status is set to 5xx
def server_error?
status.between? 500, 599
end
# whether or not the status is set to 404
def not_found?
status == 404
end
# whether or not the status is set to 400
def bad_request?
status == 400
end
# Generates a Time object from the given value.
# Used by #expires and #last_modified.
def time_for(value)
if value.is_a? Numeric
Time.at value
elsif value.respond_to? :to_s
Time.parse value.to_s
else
value.to_time
end
rescue ArgumentError => e
raise e
rescue Exception
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
end
private
# Helper method checking if a ETag value list includes the current ETag.
def etag_matches?(list, new_resource = request.post?)
return !new_resource if list == '*'
list.to_s.split(/\s*,\s*/).include? response['ETag']
end
def with_params(temp_params)
original = @params
@params = temp_params
yield
ensure
@params = original if original
end
end
# Template rendering methods. Each method takes the name of a template
# to render as a Symbol and returns a String with the rendered output,
# as well as an optional hash with additional options.
#
# `template` is either the name or path of the template as symbol
# (Use `:'subdir/myview'` for views in subdirectories), or a string
# that will be rendered.
#
# Possible options are:
# :content_type The content type to use, same arguments as content_type.
# :layout If set to something falsy, no layout is rendered, otherwise
# the specified layout is used (Ignored for `sass`)
# :layout_engine Engine to use for rendering the layout.
# :locals A hash with local variables that should be available
# in the template
# :scope If set, template is evaluate with the binding of the given
# object rather than the application instance.
# :views Views directory to use.
module Templates
module ContentTyped
attr_accessor :content_type
end
def initialize
super
@default_layout = :layout
@preferred_extension = nil
end
def erb(template, options = {}, locals = {}, &block)
render(:erb, template, options, locals, &block)
end
def haml(template, options = {}, locals = {}, &block)
render(:haml, template, options, locals, &block)
end
def sass(template, options = {}, locals = {})
options[:default_content_type] = :css
options[:exclude_outvar] = true
options[:layout] = nil
render :sass, template, options, locals
end
def scss(template, options = {}, locals = {})
options[:default_content_type] = :css
options[:exclude_outvar] = true
options[:layout] = nil
render :scss, template, options, locals
end
def builder(template = nil, options = {}, locals = {}, &block)
options[:default_content_type] = :xml
render_ruby(:builder, template, options, locals, &block)
end
def liquid(template, options = {}, locals = {}, &block)
render(:liquid, template, options, locals, &block)
end
def markdown(template, options = {}, locals = {})
options[:exclude_outvar] = true
render :markdown, template, options, locals
end
def rdoc(template, options = {}, locals = {})
render :rdoc, template, options, locals
end
def asciidoc(template, options = {}, locals = {})
render :asciidoc, template, options, locals
end
def markaby(template = nil, options = {}, locals = {}, &block)
render_ruby(:mab, template, options, locals, &block)
end
def nokogiri(template = nil, options = {}, locals = {}, &block)
options[:default_content_type] = :xml
render_ruby(:nokogiri, template, options, locals, &block)
end
def slim(template, options = {}, locals = {}, &block)
render(:slim, template, options, locals, &block)
end
def yajl(template, options = {}, locals = {})
options[:default_content_type] = :json
render :yajl, template, options, locals
end
def rabl(template, options = {}, locals = {})
Rabl.register!
render :rabl, template, options, locals
end
# Calls the given block for every possible template file in views,
# named name.ext, where ext is registered on engine.
def find_template(views, name, engine)
yield ::File.join(views, "#{name}.#{@preferred_extension}")
Tilt.default_mapping.extensions_for(engine).each do |ext|
yield ::File.join(views, "#{name}.#{ext}") unless ext == @preferred_extension
end
end
private
# logic shared between builder and nokogiri
def render_ruby(engine, template, options = {}, locals = {}, &block)
if template.is_a?(Hash)
options = template
template = nil
end
template = proc { block } if template.nil?
render engine, template, options, locals
end
def render(engine, data, options = {}, locals = {}, &block)
# merge app-level options
engine_options = settings.respond_to?(engine) ? settings.send(engine) : {}
options.merge!(engine_options) { |_key, v1, _v2| v1 }
# extract generic options
locals = options.delete(:locals) || locals || {}
views = options.delete(:views) || settings.views || './views'
layout = options[:layout]
layout = false if layout.nil? && options.include?(:layout)
eat_errors = layout.nil?
layout = engine_options[:layout] if layout.nil? || (layout == true && engine_options[:layout] != false)
layout = @default_layout if layout.nil? || (layout == true)
layout_options = options.delete(:layout_options) || {}
content_type = options.delete(:default_content_type)
content_type = options.delete(:content_type) || content_type
layout_engine = options.delete(:layout_engine) || engine
scope = options.delete(:scope) || self
exclude_outvar = options.delete(:exclude_outvar)
options.delete(:layout)
# set some defaults
options[:outvar] ||= '@_out_buf' unless exclude_outvar
options[:default_encoding] ||= settings.default_encoding
# compile and render template
begin
layout_was = @default_layout
@default_layout = false
template = compile_template(engine, data, options, views)
output = template.render(scope, locals, &block)
ensure
@default_layout = layout_was
end
# render layout
if layout
extra_options = { views: views, layout: false, eat_errors: eat_errors, scope: scope }
options = options.merge(extra_options).merge!(layout_options)
catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } }
end
if content_type
# sass-embedded returns a frozen string
output = +output
output.extend(ContentTyped).content_type = content_type
end
output
end
def compile_template(engine, data, options, views)
eat_errors = options.delete :eat_errors
template = Tilt[engine]
raise "Template engine not found: #{engine}" if template.nil?
case data
when Symbol
template_cache.fetch engine, data, options, views do
body, path, line = settings.templates[data]
if body
body = body.call if body.respond_to?(:call)
template.new(path, line.to_i, options) { body }
else
found = false
@preferred_extension = engine.to_s
find_template(views, data, template) do |file|
path ||= file # keep the initial path rather than the last one
found = File.exist?(file)
if found
path = file
break
end
end
throw :layout_missing if eat_errors && !found
template.new(path, 1, options)
end
end
when Proc
compile_block_template(template, options, &data)
when String
template_cache.fetch engine, data, options, views do
compile_block_template(template, options) { data }
end
else
raise ArgumentError, "Sorry, don't know how to render #{data.inspect}."
end
end
def compile_block_template(template, options, &body)
first_location = caller_locations.first
path = first_location.path
line = first_location.lineno
path = options[:path] || path
line = options[:line] || line
template.new(path, line.to_i, options, &body)
end
end
# Extremely simple template cache implementation.
# * Not thread-safe.
# * Size is unbounded.
# * Keys are not copied defensively, and should not be modified after
# being passed to #fetch. More specifically, the values returned by
# key#hash and key#eql? should not change.
#
# Implementation copied from Tilt::Cache.
class TemplateCache
def initialize
@cache = {}
end
# Caches a value for key, or returns the previously cached value.
# If a value has been previously cached for key then it is
# returned. Otherwise, block is yielded to and its return value
# which may be nil, is cached under key and returned.
def fetch(*key)
@cache.fetch(key) do
@cache[key] = yield
end
end
# Clears the cache.
def clear
@cache = {}
end
end
# Base class for all Sinatra applications and middleware.
class Base
include Rack::Utils
include Helpers
include Templates
URI_INSTANCE = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::RFC2396_Parser.new
attr_accessor :app, :env, :request, :response, :params
attr_reader :template_cache
def initialize(app = nil, **_kwargs)
super()
@app = app
@template_cache = TemplateCache.new
@pinned_response = nil # whether a before! filter pinned the content-type
yield self if block_given?
end
# Rack call interface.
def call(env)
dup.call!(env)
end
def call!(env) # :nodoc:
@env = env
@params = IndifferentHash.new
@request = Request.new(env)
@response = Response.new
@pinned_response = nil
template_cache.clear if settings.reload_templates
invoke { dispatch! }
invoke { error_block!(response.status) } unless @env['sinatra.error']
unless @response['content-type']
if Array === body && body[0].respond_to?(:content_type)
content_type body[0].content_type
elsif (default = settings.default_content_type)
content_type default
end
end
@response.finish
end
# Access settings defined with Base.set.
def self.settings
self
end
# Access settings defined with Base.set.
def settings
self.class.settings
end
# Exit the current block, halts any further processing
# of the request, and returns the specified response.
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
# Pass control to the next matching route.
# If there are no more matching routes, Sinatra will
# return a 404 response.
def pass(&block)
throw :pass, block
end
# Forward the request to the downstream app -- middleware only.
def forward
raise 'downstream app not set' unless @app.respond_to? :call
status, headers, body = @app.call env
@response.status = status
@response.body = body
@response.headers.merge! headers
nil
end
private
# Run filters defined on the class and all superclasses.
# Accepts an optional block to call after each filter is applied.
def filter!(type, base = settings, &block)
filter!(type, base.superclass, &block) if base.superclass.respond_to?(:filters)
base.filters[type].each do |args|
result = process_route(*args)
block.call(result) if block_given?
end
end
# Run routes defined on the class and all superclasses.
def route!(base = settings, pass_block = nil)
routes = base.routes[@request.request_method]
routes&.each do |pattern, conditions, block|
response.delete_header('content-type') unless @pinned_response
returned_pass_block = process_route(pattern, conditions) do |*args|
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
route_eval { block[*args] }
end
# don't wipe out pass_block in superclass
pass_block = returned_pass_block if returned_pass_block
end
# Run routes defined in superclass.
if base.superclass.respond_to?(:routes)
return route!(base.superclass, pass_block)
end
route_eval(&pass_block) if pass_block
route_missing
end
# Run a route block and throw :halt with the result.
def route_eval
throw :halt, yield
end
# If the current request matches pattern and conditions, fill params
# with keys and call the given block.
# Revert params afterwards.
#
# Returns pass block.
def process_route(pattern, conditions, block = nil, values = [])
route = @request.path_info
route = '/' if route.empty? && !settings.empty_path_info?
route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
params = pattern.params(route)
return unless params
params.delete('ignore') # TODO: better params handling, maybe turn it into "smart" object or detect changes
force_encoding(params)
@params = @params.merge(params) { |_k, v1, v2| v2 || v1 } if params.any?
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? { |subpattern| subpattern.is_a?(Mustermann::Regular) })
if regexp_exists
captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
values += captures
@params[:captures] = force_encoding(captures) unless captures.nil? || captures.empty?
else
values += params.values.flatten
end
catch(:pass) do
conditions.each { |c| throw :pass if c.bind(self).call == false }
block ? block[self, values] : yield(self, values)
end
rescue StandardError
@env['sinatra.error.params'] = @params
raise
ensure
params ||= {}
params.each { |k, _| @params.delete(k) } unless @env['sinatra.error.params']
end
# No matching route was found or all routes passed. The default
# implementation is to forward the request downstream when running
# as middleware (@app is non-nil); when no downstream app is set, raise
# a NotFound exception. Subclasses can override this method to perform
# custom route miss logic.
def route_missing
raise NotFound unless @app
forward
end
# Attempt to serve static files from public directory. Throws :halt when
# a matching file is found, returns nil otherwise.
def static!(options = {})
return if (public_dir = settings.public_folder).nil?
path = "#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}"
return unless valid_path?(path)
path = File.expand_path(path)
return unless path.start_with?("#{File.expand_path(public_dir)}/")
return unless File.file?(path)
env['sinatra.static_file'] = path
cache_control(*settings.static_cache_control) if settings.static_cache_control?
send_file path, options.merge(disposition: nil)
end
# Run the block with 'throw :halt' support and apply result to the response.
def invoke(&block)
res = catch(:halt, &block)
res = [res] if (Integer === res) || (String === res)
if (Array === res) && (Integer === res.first)
res = res.dup
status(res.shift)
body(res.pop)
headers(*res)
elsif res.respond_to? :each
body res
end
nil # avoid double setting the same response tuple twice
end
# Dispatch a request with error handling.
def dispatch!
# Avoid passing frozen string in force_encoding
@params.merge!(@request.params).each do |key, val|
next unless val.respond_to?(:force_encoding)
val = val.dup if val.frozen?
@params[key] = force_encoding(val)
end
invoke do
static! if settings.static? && (request.get? || request.head?)
filter! :before do
@pinned_response = !response['content-type'].nil?
end
route!
end
rescue ::Exception => e
invoke { handle_exception!(e) }
ensure
begin
filter! :after unless env['sinatra.static_file']
rescue ::Exception => e
invoke { handle_exception!(e) } unless @env['sinatra.error']
end
end
# Error handling during requests.
def handle_exception!(boom)
error_params = @env['sinatra.error.params']
@params = @params.merge(error_params) if error_params
@env['sinatra.error'] = boom
http_status = if boom.is_a? Sinatra::Error
if boom.respond_to? :http_status
boom.http_status
elsif settings.use_code? && boom.respond_to?(:code)
boom.code
end
end
http_status = 500 unless http_status&.between?(400, 599)
status(http_status)
if server_error?
dump_errors! boom if settings.dump_errors?
raise boom if settings.show_exceptions? && (settings.show_exceptions != :after_handler)
elsif not_found?
headers['X-Cascade'] = 'pass' if settings.x_cascade?
end
if (res = error_block!(boom.class, boom) || error_block!(status, boom))
return res
end
if not_found? || bad_request?
if boom.message && boom.message != boom.class.name
body Rack::Utils.escape_html(boom.message)
else
content_type 'text/html'
body "#{not_found? ? 'Not Found' : 'Bad Request'}
"
end
end
return unless server_error?
raise boom if settings.raise_errors? || settings.show_exceptions?
error_block! Exception, boom
end
# Find an custom error block for the key(s) specified.
def error_block!(key, *block_params)
base = settings
while base.respond_to?(:errors)
args_array = base.errors[key]
next base = base.superclass unless args_array
args_array.reverse_each do |args|
first = args == args_array.first
args += [block_params]
resp = process_route(*args)
return resp unless resp.nil? && !first
end
end
return false unless key.respond_to?(:superclass) && (key.superclass < Exception)
error_block!(key.superclass, *block_params)
end
def dump_errors!(boom)
if boom.respond_to?(:detailed_message)
msg = boom.detailed_message(highlight: false)
if msg =~ /\A(.*?)(?: \(#{ Regexp.quote(boom.class.to_s) }\))?\n/
msg = $1
additional_msg = $'.lines(chomp: true)
else
additional_msg = []
end
else
msg = boom.message
additional_msg = []
end
msg = ["#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{boom.class} - #{msg}:", *additional_msg, *boom.backtrace].join("\n\t")
@env['rack.errors'].puts(msg)
end
class << self
CALLERS_TO_IGNORE = [ # :nodoc:
%r{/sinatra(/(base|main|show_exceptions))?\.rb$}, # all sinatra code
%r{lib/tilt.*\.rb$}, # all tilt code
/^\(.*\)$/, # generated code
/\/bundled_gems.rb$/, # ruby >= 3.3 with bundler >= 2.5
%r{rubygems/(custom|core_ext/kernel)_require\.rb$}, # rubygems require hacks
/active_support/, # active_support require hacks
%r{bundler(/(?:runtime|inline))?\.rb}, # bundler require hacks
/= 1.9.2
%r{zeitwerk/(core_ext/)?kernel\.rb} # Zeitwerk kernel#require decorator
].freeze
attr_reader :routes, :filters, :templates, :errors, :on_start_callback, :on_stop_callback
def callers_to_ignore
CALLERS_TO_IGNORE
end
# Removes all routes, filters, middleware and extension hooks from the
# current class (not routes/filters/... defined by its superclass).
def reset!
@conditions = []
@routes = {}
@filters = { before: [], after: [] }
@errors = {}
@middleware = []
@prototype = nil
@extensions = []
@templates = if superclass.respond_to?(:templates)
Hash.new { |_hash, key| superclass.templates[key] }
else
{}
end
end
# Extension modules registered on this class and all superclasses.
def extensions
if superclass.respond_to?(:extensions)
(@extensions + superclass.extensions).uniq
else
@extensions
end
end
# Middleware used in this class and all superclasses.
def middleware
if superclass.respond_to?(:middleware)
superclass.middleware + @middleware
else
@middleware
end
end
# Sets an option to the given value. If the value is a proc,
# the proc will be called every time the option is accessed.
def set(option, value = (not_set = true), ignore_setter = false, &block)
raise ArgumentError if block && !not_set
if block
value = block
not_set = false
end
if not_set
raise ArgumentError unless option.respond_to?(:each)
option.each { |k, v| set(k, v) }
return self
end
if respond_to?("#{option}=") && !ignore_setter
return __send__("#{option}=", value)
end
setter = proc { |val| set option, val, true }
getter = proc { value }
case value
when Proc
getter = value
when Symbol, Integer, FalseClass, TrueClass, NilClass
getter = value.inspect
when Hash
setter = proc do |val|
val = value.merge val if Hash === val
set option, val, true
end
end
define_singleton("#{option}=", setter)
define_singleton(option, getter)
define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
self
end
# Same as calling `set :option, true` for each of the given options.
def enable(*opts)
opts.each { |key| set(key, true) }
end
# Same as calling `set :option, false` for each of the given options.
def disable(*opts)
opts.each { |key| set(key, false) }
end
# Define a custom error handler. Optionally takes either an Exception
# class, or an HTTP status code to specify which errors should be
# handled.
def error(*codes, &block)
args = compile! 'ERROR', /.*/, block
codes = codes.flat_map(&method(:Array))
codes << Exception if codes.empty?
codes << Sinatra::NotFound if codes.include?(404)
codes.each { |c| (@errors[c] ||= []) << args }
end
# Sugar for `error(404) { ... }`
def not_found(&block)
error(404, &block)
end
# Define a named template. The block must return the template source.
def template(name, &block)
filename, line = caller_locations.first
templates[name] = [block, filename, line.to_i]
end
# Define the layout template. The block must return the template source.
def layout(name = :layout, &block)
template name, &block
end
# Load embedded templates from the file; uses the caller's __FILE__
# when no file is specified.
def inline_templates=(file = nil)
file = (caller_files.first || File.expand_path($0)) if file.nil? || file == true
begin
io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2)
rescue Errno::ENOENT
app, data = nil
end
return unless data
encoding = if app && app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
$2
else
settings.default_encoding
end
lines = app.count("\n") + 1
template = nil
force_encoding data, encoding
data.each_line do |line|
lines += 1
if line =~ /^@@\s*(.*\S)\s*$/
template = force_encoding(String.new, encoding)
templates[$1.to_sym] = [template, file, lines]
elsif template
template << line
end
end
end
# Lookup or register a mime type in Rack's mime registry.
def mime_type(type, value = nil)
return type if type.nil?
return type.to_s if type.to_s.include?('/')
type = ".#{type}" unless type.to_s[0] == '.'
return Rack::Mime.mime_type(type, nil) unless value
Rack::Mime::MIME_TYPES[type] = value
end
# provides all mime types matching type, including deprecated types:
# mime_types :html # => ['text/html']
# mime_types :js # => ['application/javascript', 'text/javascript']
def mime_types(type)
type = mime_type type
if type =~ %r{^application/(xml|javascript)$}
[type, "text/#{$1}"]
elsif type =~ %r{^text/(xml|javascript)$}
[type, "application/#{$1}"]
else
[type]
end
end
# Define a before filter; runs before all requests within the same
# context as route handlers and may access/modify the request and
# response.
def before(path = /.*/, **options, &block)
add_filter(:before, path, **options, &block)
end
# Define an after filter; runs after all requests within the same
# context as route handlers and may access/modify the request and
# response.
def after(path = /.*/, **options, &block)
add_filter(:after, path, **options, &block)
end
# add a filter
def add_filter(type, path = /.*/, **options, &block)
filters[type] << compile!(type, path, block, **options)
end
def on_start(&on_start_callback)
@on_start_callback = on_start_callback
end
def on_stop(&on_stop_callback)
@on_stop_callback = on_stop_callback
end
# Add a route condition. The route is considered non-matching when the
# block returns false.
def condition(name = "#{caller.first[/`.*'/]} condition", &block)
@conditions << generate_method(name, &block)
end
def public=(value)
warn_for_deprecation ':public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead'
set(:public_folder, value)
end
def public_dir=(value)
self.public_folder = value
end
def public_dir
public_folder
end
# Defining a `GET` handler also automatically defines
# a `HEAD` handler.
def get(path, opts = {}, &block)
conditions = @conditions.dup
route('GET', path, opts, &block)
@conditions = conditions
route('HEAD', path, opts, &block)
end
def put(path, opts = {}, &block) route 'PUT', path, opts, &block end
def post(path, opts = {}, &block) route 'POST', path, opts, &block end
def delete(path, opts = {}, &block) route 'DELETE', path, opts, &block end
def head(path, opts = {}, &block) route 'HEAD', path, opts, &block end
def options(path, opts = {}, &block) route 'OPTIONS', path, opts, &block end
def patch(path, opts = {}, &block) route 'PATCH', path, opts, &block end
def link(path, opts = {}, &block) route 'LINK', path, opts, &block end
def unlink(path, opts = {}, &block) route 'UNLINK', path, opts, &block end
# Makes the methods defined in the block and in the Modules given
# in `extensions` available to the handlers and templates
def helpers(*extensions, &block)
class_eval(&block) if block_given?
include(*extensions) if extensions.any?
end
# Register an extension. Alternatively take a block from which an
# extension will be created and registered on the fly.
def register(*extensions, &block)
extensions << Module.new(&block) if block_given?
@extensions += extensions
extensions.each do |extension|
extend extension
extension.registered(self) if extension.respond_to?(:registered)
end
end
def development?; environment == :development end
def production?; environment == :production end
def test?; environment == :test end
# Set configuration options for Sinatra and/or the app.
# Allows scoping of settings for certain environments.
def configure(*envs)
yield self if envs.empty? || envs.include?(environment.to_sym)
end
# Use the specified Rack middleware
def use(middleware, *args, &block)
@prototype = nil
@middleware << [middleware, args, block]
end
ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
# Stop the self-hosted server if running.
def quit!
return unless running?
# Use Thin's hard #stop! if available, otherwise just #stop.
running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
warn '== Sinatra has ended his set (crowd applauds)' unless suppress_messages?
set :running_server, nil
set :handler_name, nil
on_stop_callback.call unless on_stop_callback.nil?
end
alias stop! quit!
# Run the Sinatra app as a self-hosted server using
# Puma, Falcon (in that order). If given a block, will call
# with the constructed handler once we have taken the stage.
def run!(options = {}, &block)
unless defined?(Rackup::Handler)
rackup_warning = <<~MISSING_RACKUP
Sinatra could not start, the required gems weren't found!
Add them to your bundle with:
bundle add rackup puma
or install them with:
gem install rackup puma
MISSING_RACKUP
warn rackup_warning
exit 1
end
return if running?
set options
handler = Rackup::Handler.pick(server)
handler_name = handler.name.gsub(/.*::/, '')
server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
server_settings.merge!(Port: port, Host: bind)
begin
start_server(handler, server_settings, handler_name, &block)
rescue Errno::EADDRINUSE
warn "== Someone is already performing on port #{port}!"
raise
ensure
quit!
end
end
alias start! run!
# Check whether the self-hosted server is running or not.
def running?
running_server?
end
# The prototype instance used to process requests.
def prototype
@prototype ||= new
end
# Create a new instance without middleware in front of it.
alias new! new unless method_defined? :new!
# Create a new instance of the class fronted by its middleware
# pipeline. The object is guaranteed to respond to #call but may not be
# an instance of the class new was called on.
def new(*args, &block)
instance = new!(*args, &block)
Wrapper.new(build(instance).to_app, instance)
end
ruby2_keywords :new if respond_to?(:ruby2_keywords, true)
# Creates a Rack::Builder instance with all the middleware set up and
# the given +app+ as end point.
def build(app)
builder = Rack::Builder.new
setup_default_middleware builder
setup_middleware builder
builder.run app
builder
end
def call(env)
synchronize { prototype.call(env) }
end
# Like Kernel#caller but excluding certain magic entries and without
# line / method information; the resulting array contains filenames only.
def caller_files
cleaned_caller(1).flatten
end
private
# Starts the server by running the Rack Handler.
def start_server(handler, server_settings, handler_name)
# Ensure we initialize middleware before startup, to match standard Rack
# behavior, by ensuring an instance exists:
prototype
# Run the instance we created:
handler.run(self, **server_settings) do |server|
unless suppress_messages?
warn "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
end
setup_traps
set :running_server, server
set :handler_name, handler_name
server.threaded = settings.threaded if server.respond_to? :threaded=
on_start_callback.call unless on_start_callback.nil?
yield server if block_given?
end
end
def suppress_messages?
handler_name =~ /cgi/i || quiet
end
def setup_traps
return unless traps?
at_exit { quit! }
%i[INT TERM].each do |signal|
old_handler = trap(signal) do
quit!
old_handler.call if old_handler.respond_to?(:call)
end
end
set :traps, false
end
# Dynamically defines a method on settings.
def define_singleton(name, content = Proc.new)
singleton_class.class_eval do
undef_method(name) if method_defined? name
String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
end
end
# Condition for matching host name. Parameter might be String or Regexp.
def host_name(pattern)
condition { pattern === request.host }
end
# Condition for matching user agent. Parameter should be Regexp.
# Will set params[:agent].
def user_agent(pattern)
condition do
if request.user_agent.to_s =~ pattern
@params[:agent] = $~[1..-1]
true
else
false
end
end
end
alias agent user_agent
# Condition for matching mimetypes. Accepts file extensions.
def provides(*types)
types.map! { |t| mime_types(t) }
types.flatten!
condition do
response_content_type = response['content-type']
preferred_type = request.preferred_type(types)
if response_content_type
types.include?(response_content_type) || types.include?(response_content_type[/^[^;]+/])
elsif preferred_type
params = (preferred_type.respond_to?(:params) ? preferred_type.params : {})
content_type(preferred_type, params)
true
else
false
end
end
end
def route(verb, path, options = {}, &block)
enable :empty_path_info if path == '' && empty_path_info.nil?
signature = compile!(verb, path, block, **options)
(@routes[verb] ||= []) << signature
invoke_hook(:route_added, verb, path, block)
signature
end
def invoke_hook(name, *args)
extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
end
def generate_method(method_name, &block)
define_method(method_name, &block)
method = instance_method method_name
remove_method method_name
method
end
def compile!(verb, path, block, **options)
# Because of self.options.host
host_name(options.delete(:host)) if options.key?(:host)
# Pass Mustermann opts to compile()
route_mustermann_opts = options.key?(:mustermann_opts) ? options.delete(:mustermann_opts) : {}.freeze
options.each_pair { |option, args| send(option, *args) }
pattern = compile(path, route_mustermann_opts)
method_name = "#{verb} #{path}"
unbound_method = generate_method(method_name, &block)
conditions = @conditions
@conditions = []
wrapper = block.arity.zero? ?
proc { |a, _p| unbound_method.bind(a).call } :
proc { |a, p| unbound_method.bind(a).call(*p) }
[pattern, conditions, wrapper]
end
def compile(path, route_mustermann_opts = {})
Mustermann.new(path, **mustermann_opts.merge(route_mustermann_opts))
end
def setup_default_middleware(builder)
builder.use ExtendedRack
builder.use ShowExceptions if show_exceptions?
builder.use Rack::MethodOverride if method_override?
builder.use Rack::Head
setup_logging builder
setup_sessions builder
setup_protection builder
setup_host_authorization builder
end
def setup_middleware(builder)
middleware.each { |c, a, b| builder.use(c, *a, &b) }
end
def setup_logging(builder)
if logging?
setup_common_logger(builder)
setup_custom_logger(builder)
elsif logging == false
setup_null_logger(builder)
end
end
def setup_null_logger(builder)
builder.use Sinatra::Middleware::Logger, ::Logger::FATAL
end
def setup_common_logger(builder)
builder.use Sinatra::CommonLogger
end
def setup_custom_logger(builder)
if logging.respond_to? :to_int
builder.use Sinatra::Middleware::Logger, logging
else
builder.use Sinatra::Middleware::Logger
end
end
def setup_protection(builder)
return unless protection?
options = Hash === protection ? protection.dup : {}
options = {
img_src: "'self' data:",
font_src: "'self'"
}.merge options
protect_session = options.fetch(:session) { sessions? }
options[:without_session] = !protect_session
options[:reaction] ||= :drop_session
builder.use Rack::Protection, options
end
def setup_host_authorization(builder)
builder.use Rack::Protection::HostAuthorization, host_authorization
end
def setup_sessions(builder)
return unless sessions?
options = {}
options[:secret] = session_secret if session_secret?
options.merge! sessions.to_hash if sessions.respond_to? :to_hash
builder.use session_store, options
end
def inherited(subclass)
subclass.reset!
subclass.set :app_file, caller_files.first unless subclass.app_file?
super
end
@@mutex = Mutex.new
def synchronize(&block)
if lock?
@@mutex.synchronize(&block)
else
yield
end
end
# used for deprecation warnings
def warn_for_deprecation(message)
warn message + "\n\tfrom #{cleaned_caller.first.join(':')}"
end
# Like Kernel#caller but excluding certain magic entries
def cleaned_caller(keep = 3)
caller(1)
.map! { |line| line.split(/:(?=\d|in )/, 3)[0, keep] }
.reject { |file, *_| callers_to_ignore.any? { |pattern| file =~ pattern } }
end
end
# Force data to specified encoding. It defaults to settings.default_encoding
# which is UTF-8 by default
def self.force_encoding(data, encoding = default_encoding)
return if data == settings || data.is_a?(Tempfile)
if data.respond_to? :force_encoding
data.force_encoding(encoding).encode!
elsif data.respond_to? :each_value
data.each_value { |v| force_encoding(v, encoding) }
elsif data.respond_to? :each
data.each { |v| force_encoding(v, encoding) }
end
data
end
def force_encoding(*args)
settings.force_encoding(*args)
end
reset!
set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
set :raise_errors, proc { test? }
set :dump_errors, proc { !test? }
set :show_exceptions, proc { development? }
set :sessions, false
set :session_store, Rack::Session::Cookie
set :logging, false
set :protection, true
set :method_override, false
set :use_code, false
set :default_encoding, 'utf-8'
set :x_cascade, true
set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" }
settings.add_charset << %r{^text/}
set :mustermann_opts, {}
set :default_content_type, 'text/html'
# explicitly generating a session secret eagerly to play nice with preforking
begin
require 'securerandom'
set :session_secret, SecureRandom.hex(64)
rescue LoadError, NotImplementedError, RuntimeError
# SecureRandom raises a NotImplementedError if no random device is available
# RuntimeError raised due to broken openssl backend: https://bugs.ruby-lang.org/issues/19230
set :session_secret, format('%064x', Kernel.rand((2**256) - 1))
end
class << self
alias methodoverride? method_override?
alias methodoverride= method_override=
end
set :run, false # start server via at-exit hook?
set :running_server, nil
set :handler_name, nil
set :traps, true
set :server, %w[webrick]
set :bind, proc { development? ? 'localhost' : '0.0.0.0' }
set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
set :quiet, false
set :host_authorization, ->() do
if development?
{
permitted_hosts: [
"localhost",
".localhost",
".test",
IPAddr.new("0.0.0.0/0"),
IPAddr.new("::/0"),
]
}
else
{}
end
end
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
server.unshift 'thin' if ruby_engine != 'jruby'
server.unshift 'falcon' if ruby_engine != 'jruby'
server.unshift 'trinidad' if ruby_engine == 'jruby'
server.unshift 'puma'
set :absolute_redirects, true
set :prefixed_redirects, false
set :empty_path_info, nil
set :strict_paths, true
set :app_file, nil
set :root, proc { app_file && File.expand_path(File.dirname(app_file)) }
set :views, proc { root && File.join(root, 'views') }
set :reload_templates, proc { development? }
set :lock, false
set :threaded, true
set :public_folder, proc { root && File.join(root, 'public') }
set :static, proc { public_folder && File.exist?(public_folder) }
set :static_cache_control, false
error ::Exception do
response.status = 500
content_type 'text/html'
'Internal Server Error
'
end
configure :development do
get '/__sinatra__/:image.png' do
filename = __dir__ + "/images/#{params[:image].to_i}.png"
content_type :png
send_file filename
end
error NotFound do
content_type 'text/html'
if instance_of?(Sinatra::Application)
code = <<-RUBY.gsub(/^ {12}/, '')
#{request.request_method.downcase} '#{request.path_info}' do
"Hello World"
end
RUBY
else
code = <<-RUBY.gsub(/^ {12}/, '')
class #{self.class}
#{request.request_method.downcase} '#{request.path_info}' do
"Hello World"
end
end
RUBY
file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(%r{^/}, '')
code = "# in #{file}\n#{code}" unless file.empty?
end
<<-HTML.gsub(/^ {10}/, '')
Sinatra doesn’t know this ditty.
Try this:
#{Rack::Utils.escape_html(code)}
HTML
end
end
end
# Execution context for classic style (top-level) applications. All
# DSL methods executed on main are delegated to this class.
#
# The Application class should not be subclassed, unless you want to
# inherit all settings, routes, handlers, and error pages from the
# top-level. Subclassing Sinatra::Base is highly recommended for
# modular applications.
class Application < Base
set :logging, proc { !test? }
set :method_override, true
set :run, proc { !test? }
set :app_file, nil
def self.register(*extensions, &block) # :nodoc:
added_methods = extensions.flat_map(&:public_instance_methods)
Delegator.delegate(*added_methods)
super(*extensions, &block)
end
end
# Sinatra delegation mixin. Mixing this module into an object causes all
# methods to be delegated to the Sinatra::Application class. Used primarily
# at the top-level.
module Delegator # :nodoc:
def self.delegate(*methods)
methods.each do |method_name|
define_method(method_name) do |*args, &block|
return super(*args, &block) if respond_to? method_name
Delegator.target.send(method_name, *args, &block)
end
# ensure keyword argument passing is compatible with ruby >= 2.7
ruby2_keywords(method_name) if respond_to?(:ruby2_keywords, true)
private method_name
end
end
delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
:template, :layout, :before, :after, :error, :not_found, :configure,
:set, :mime_type, :enable, :disable, :use, :development?, :test?,
:production?, :helpers, :settings, :register, :on_start, :on_stop
class << self
attr_accessor :target
end
self.target = Application
end
class Wrapper
def initialize(stack, instance)
@stack = stack
@instance = instance
end
def settings
@instance.settings
end
def helpers
@instance
end
def call(env)
@stack.call(env)
end
def inspect
"#<#{@instance.class} app_file=#{settings.app_file.inspect}>"
end
end
# Create a new Sinatra application; the block is evaluated in the class scope.
def self.new(base = Base, &block)
base = Class.new(base)
base.class_eval(&block) if block_given?
base
end
# Extend the top-level DSL with the modules provided.
def self.register(*extensions, &block)
Delegator.target.register(*extensions, &block)
end
# Include the helper modules provided in Sinatra's request context.
def self.helpers(*extensions, &block)
Delegator.target.helpers(*extensions, &block)
end
# Use the middleware for classic applications.
def self.use(*args, &block)
Delegator.target.use(*args, &block)
end
end
sinatra-4.1.1/lib/sinatra/images/ 0000775 0000000 0000000 00000000000 14717402212 0016632 5 ustar 00root root 0000000 0000000 sinatra-4.1.1/lib/sinatra/images/404.png 0000664 0000000 0000000 00000044715 14717402212 0017662 0 ustar 00root root 0000000 0000000 PNG
IHDR , ) ҈ IIDATxA 0p@
L妀 !HH? AB@B>vΰGqLѭBd0WS>T]ή%vu W?+%T(=Cn]!3FO`Irfm `o/QܱA'tDvK1x;dOB%Lh {eƛ;'>Y]'sïFNsIݐ/
@-Y](B}dpL2Ah_V ~nF]'YKMDf\~S0!ڔeQy+%_1
kLft9g9W`!>aDH`$<#aXY^|jnqV7Tz
Lf{a⒟r%L8ILB.¸p3q8YQڒMtw@hz|ԫ$1;@32M# >Y,vçg0P\ۧQm,㰾aJ!T#x>u ~:}wcPZyR{S4!)G2w̱26˪ Ci\,a,ϛ!!&=9qF Hsea(\4W@[u60@ii+'_/Pt ܛ|L*OmJNKB&u~l6]C5
V2{(,8
Ui@n/W
36~tH,]kNV'=xӃh0SŧF\zhB ӊ~)9[HB\;9Rlc?v$ 顥qwͲ@ȪAS_}H` D1S;9ן^!п{B9%Xs= !1L}H,zh{38!?y*dM@HOFuQ`/{
t(qtA҈U\AIi(I
U*]J,p$(6I3<`ό_#YWΌ@p?!z+]fᄟJG VrA.l/^흘(
e4bbNB!@zNok7#_"(bq6B" ET$OL'(09sO~v/~]Ǻ{>çO~`V ] !.Nw-K wp@W+׃Ep(:`{gff~wɩB4xuݿ~}O?|ܽBC u\vk@Xцk3[_-nIϿxwK(Zֶ퇛7oذ۷odz{Ǐ|BJ*R%M6Yf@b)$ΆJwn>kmƍoBtzX*;5%ĉwޅrDb$TtJd~suzLbsCdlgW! ]n*i!&"x\2xs2ћ7]?8x'OcwBNkG dh`:G7t^xLO&=^2JQ`wm̡+ϏLO#gg6%` O`l67::z/[PNIX A&iAzL}E<[Stp5Ro.[e|a[@ DKKK48?11>6O& 5Cxv, ݽeuuH$b|~4"?13jhEan97ڹND\KAҌ3RP劋0R5)`N>d2U=C4754$\$AfX)In>SM\
̐
3G<@&bC ZSꉳJٹ\>!eOě|_.Ɋ9X9-U*Ŀ3x}; C F}-6éTk "|.1-. x.偺Bf$0>b.LVZ%~'pUuTB~EZ<?"6' "Bl:NDիWq]* 'H#?3¡%%(AR $t5i6a@d%PR$!G O-d:=p+pHUq(_5D