- Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8"
-
-
- Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"
-
+
+ # This must be defined at the top level /feeds/ directory
+ # Both directives are required
+ XSendFile on
+ XSendFilePath /standardebooks.org/web/www/feeds
# Emit content-types for RSS/Atom feeds
diff --git a/config/sql/se/FeedUserAgents.sql b/config/sql/se/FeedUserAgents.sql
new file mode 100644
index 00000000..94c6b8c8
--- /dev/null
+++ b/config/sql/se/FeedUserAgents.sql
@@ -0,0 +1,6 @@
+CREATE TABLE `FeedUserAgents` (
+ `UserAgentId` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `UserAgent` text NOT NULL,
+ `Created` datetime NOT NULL,
+ PRIMARY KEY (`UserAgentId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
diff --git a/lib/Core.php b/lib/Core.php
index e1822d80..f55686d1 100644
--- a/lib/Core.php
+++ b/lib/Core.php
@@ -37,6 +37,12 @@ if($GLOBALS['User'] === null){
// log them in while we're here.
$session = new Session();
- $session->Create($httpBasicAuthLogin);
+ try{
+ $session->Create($httpBasicAuthLogin);
+ $GLOBALS['User'] = $session->User;
+ }
+ catch(Exception $ex){
+ // Do nothing
+ }
}
}
diff --git a/www/bulk-downloads/download.php b/www/bulk-downloads/download.php
index 25392d68..59c88204 100644
--- a/www/bulk-downloads/download.php
+++ b/www/bulk-downloads/download.php
@@ -26,7 +26,7 @@ try{
// Everything OK, serve the file using Apache.
// The xsendfile Apache module tells Apache to serve the file, including not-modified or etag headers.
- // Much more efficien than reading it in PHP and outputting it that way.
+ // Much more efficient than reading it in PHP and outputting it that way.
header('X-Sendfile: ' . WEB_ROOT . $path);
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . basename($path) . '"');
diff --git a/www/feeds/atom/index.php b/www/feeds/atom/index.php
index 1b0df25b..2f7f52c4 100644
--- a/www/feeds/atom/index.php
+++ b/www/feeds/atom/index.php
@@ -17,7 +17,7 @@ require_once('Core.php');
The fifteen latest Standard Ebooks, most-recently-released first.
- All ebooks
+ All ebooks
if($GLOBALS['User'] !== null){ ?>https://= rawurlencode($GLOBALS['User']->Email) ?>@= SITE_DOMAIN ?> }else{ ?>= SITE_URL ?> } ?>/feeds/atom/all
All Standard Ebooks, most-recently-released first.
diff --git a/www/feeds/collection.php b/www/feeds/collection.php
index c272ccd0..87e9c55f 100644
--- a/www/feeds/collection.php
+++ b/www/feeds/collection.php
@@ -68,7 +68,7 @@ catch(Safe\Exceptions\ApcuException $ex){
foreach($feeds as $feed){ ?>
-
-
= Formatter::ToPlainText($feed->Label) ?>
+ = Formatter::ToPlainText($feed->Label) ?>
if($GLOBALS['User'] !== null){ ?>https://= rawurlencode($GLOBALS['User']->Email) ?>@= SITE_DOMAIN ?> }else{ ?>= SITE_URL ?> } ?>= Formatter::ToPlainText($feed->Url) ?>
} ?>
diff --git a/www/feeds/download.php b/www/feeds/download.php
new file mode 100644
index 00000000..0e394dbe
--- /dev/null
+++ b/www/feeds/download.php
@@ -0,0 +1,72 @@
+
+require_once('Core.php');
+
+use function Safe\preg_match;
+
+// This page is blocked by HTTP Basic auth.
+// Basic authorization is handled in Core.php. By the time we get here,
+// a valid user has a session.
+
+$path = HttpInput::Str(GET, 'path', false) ?? '';
+$isUserAgentAllowed = false;
+
+try{
+ $path = '/feeds/' . $path;
+
+ if(!is_file(WEB_ROOT . $path)){
+ throw new Exceptions\InvalidFileException();
+ }
+
+ // Certain user agents may bypass login entirely
+ if(isset($_SERVER['HTTP_USER_AGENT'])){
+ $isUserAgentAllowed = (bool)Db::QueryInt('select count(*) from FeedUserAgents where instr(?, UserAgent) limit 1', [$_SERVER['HTTP_USER_AGENT']]);
+ }
+
+ if(!$isUserAgentAllowed){
+ if($GLOBALS['User'] === null){
+ throw new Exceptions\LoginRequiredException();
+ }
+
+ if(!preg_match('/\.xml$/ius', $path)){
+ throw new Exceptions\InvalidPermissionsException();
+ }
+
+ if(!$GLOBALS['User']->Benefits->CanAccessFeeds){
+ throw new Exceptions\InvalidPermissionsException();
+ }
+ }
+
+ // Everything OK, serve the file using Apache.
+ // The xsendfile Apache module tells Apache to serve the file, including not-modified or etag headers.
+ // Much more efficient than reading it in PHP and outputting it that way.
+ header('X-Sendfile: ' . WEB_ROOT . $path);
+
+ if(preg_match('/^\/feeds\/opds/', $path)){
+ header('Content-Type: application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8');
+
+ if(preg_match('/\/index\.xml$/', $path)){
+ header('Content-Type: application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8');
+ }
+ }
+ elseif(preg_match('/^\/feeds\/rss/', $path)){
+ header('Content-Type: application/rss+xml');
+ }
+ elseif(preg_match('/^\/feeds\/atom/', $path)){
+ header('Content-Type: application/atom+xml');
+ }
+
+ exit();
+}
+catch(Exceptions\LoginRequiredException $ex){
+ header('WWW-Authenticate: Basic realm="Enter your Patrons Circle email address and leave the password empty."');
+ http_response_code(401);
+}
+catch(Exceptions\InvalidPermissionsException $ex){
+ http_response_code(403);
+}
+catch(Exceptions\InvalidFileException $ex){
+ Template::Emit404();
+}
+
+// Print the login info page
+include(WEB_ROOT . '/feeds/401.php');
diff --git a/www/feeds/get.php b/www/feeds/get.php
index 5dcadbd5..f70837c6 100644
--- a/www/feeds/get.php
+++ b/www/feeds/get.php
@@ -64,7 +64,7 @@ catch(Exceptions\InvalidCollectionException $ex){
} ?>
diff --git a/www/feeds/index.php b/www/feeds/index.php
index eb487625..adba00a4 100644
--- a/www/feeds/index.php
+++ b/www/feeds/index.php
@@ -19,7 +19,7 @@ require_once('Core.php');
To connect your ereading app to our catalog, enter the URL below when prompted by your app: