[LON-CAPA-cvs] cvs: doc /loncapafiles loncapafiles.lpml loncom/interface loncommon.pm lonextresedit.pm lonexturlcheck.pm lonsyllabus.pm rat lonwrapper.pm
raeburn
raeburn at source.lon-capa.org
Wed May 1 22:12:43 EDT 2019
raeburn Thu May 2 02:12:43 2019 EDT
Added files:
/loncom/interface lonexturlcheck.pm
Modified files:
/loncom/interface lonextresedit.pm loncommon.pm lonsyllabus.pm
/rat lonwrapper.pm
/doc/loncapafiles loncapafiles.lpml
Log:
- Bug 6910
Gracefully handle display (and preview) for External Resources for which
Content-Security-Policy or X-Frame-Options prevent display in iframe in LC.
-------------- next part --------------
Index: loncom/interface/lonextresedit.pm
diff -u loncom/interface/lonextresedit.pm:1.27 loncom/interface/lonextresedit.pm:1.28
--- loncom/interface/lonextresedit.pm:1.27 Wed Nov 7 18:56:48 2018
+++ loncom/interface/lonextresedit.pm Thu May 2 02:12:18 2019
@@ -1,7 +1,7 @@
# The LearningOnline Network
# Documents
#
-# $Id: lonextresedit.pm,v 1.27 2018/11/07 18:56:48 raeburn Exp $
+# $Id: lonextresedit.pm,v 1.28 2019/05/02 02:12:18 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -767,6 +767,12 @@
invurl => 'Invalid URL',
titbl => 'Title is blank',
invtool => 'Please select an external tool',
+ mixfra => 'Show preview in pop-up? (http in https page + no framing)',
+ mixonly => 'Show preview in pop-up? (http in https page)',
+ fraonly => 'Show preview in pop-up? (framing disallowed)',
+ nopopup => 'Pop-up blocked',
+ nopriv => 'Insufficient privileges to use preview',
+ badurl => 'URL is not: http://hostname/path or https://hostname/path',
);
&js_escape(\%js_lt);
@@ -959,17 +965,74 @@
var url = document.getElementById(caller).value;
if (regexp.test(url)) {
var http_regex = /^http\:\/\//gi;
+ var mixed = 0;
+ var noiframe = 0;
+ var nopriv = 0;
+ var badurl = 0;
+ var name = "externalpreview";
if ((protocol == 'https') && (http_regex.test(url))) {
- window.open(url,"externalpreview","height=400,width=500,scrollbars=1,resizable=1,menubar=0,location=1");
- } else {
- openMyModal(url,500,400,'yes');
+ mixed = 1;
+ }
+ var http = new XMLHttpRequest();
+ var lcurl = "/adm/exturlcheck";
+ var params = "exturl="+url;
+ http.open("POST",lcurl, true);
+ http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ http.onreadystatechange = function() {
+ if (http.readyState == 4) {
+ if (http.status == 200) {
+ if (http.responseText == 1) {
+ noiframe = 1;
+ } else if (http.responseText == -1) {
+ nopriv = 1;
+ } else if (http.responseText == 0) {
+ badurl = 1;
+ }
+ openPreviewWindow(url,name,noiframe,mixed,nopriv,badurl);
+ }
+ }
}
+ http.send(params);
} else {
alert("$js_lt{'invurl'}");
}
}
}
+var previewLCWindow = null;
+function openPreviewWindow(url,name,noiframe,mixed,nopriv,badurl) {
+ if (previewLCWindow !=null) {
+ previewLCWindow.close();
+ }
+ if (badurl) {
+ alert("$js_lt{'badurl'}");
+ } else if (nopriv) {
+ alert("$js_lt{'nopriv'}");
+ } else if ((noiframe == 1) || (mixed == 1)) {
+ var encurl = encodeURI(url);
+ var msg;
+ if (mixed == 1) {
+ if (noiframe == 1) {
+ msg = "$js_lt{'mixfra'}";
+ } else {
+ msg = "$js_lt{'mixonly'}";
+ }
+ } else {
+ msg = "$js_lt{'fraonly'}";
+ }
+ if (confirm(msg)) {
+ previewLCWindow = window.open(url,name,"height=400,width=500,scrollbars=1,resizable=1,menubar=0,location=1");
+ if (previewLCWindow != null) {
+ previewLCWindow.focus();
+ } else {
+ alert("$js_lt{'nopopup'}");
+ }
+ }
+ } else {
+ openMyModal(url,500,400,'yes');
+ }
+}
+
function updateExttool(caller,form,supplementalflag) {
var prefix = '';
if (supplementalflag == 1) {
Index: loncom/interface/loncommon.pm
diff -u loncom/interface/loncommon.pm:1.1327 loncom/interface/loncommon.pm:1.1328
--- loncom/interface/loncommon.pm:1.1327 Wed Apr 24 01:44:30 2019
+++ loncom/interface/loncommon.pm Thu May 2 02:12:18 2019
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# a pile of common routines
#
-# $Id: loncommon.pm,v 1.1327 2019/04/24 01:44:30 raeburn Exp $
+# $Id: loncommon.pm,v 1.1328 2019/05/02 02:12:18 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -72,6 +72,7 @@
use Apache::courseclassifier();
use LONCAPA qw(:DEFAULT :match);
use LONCAPA::LWPReq;
+use HTTP::Request;
use DateTime::TimeZone;
use DateTime::Locale;
use Encode();
@@ -18191,6 +18192,120 @@
return $init;
}
+sub is_nonframeable {
+ my ($url,$absolute,$hostname,$ip) = @_;
+ my $uselink;
+ my $request = new HTTP::Request('HEAD',$url);
+ my $response = &LONCAPA::LWPReq::makerequest('',$request,'','',5);
+ if ($response->is_success()) {
+ my $secpolicy = lc($response->header('content-security-policy'));
+ my $xframeop = lc($response->header('x-frame-options'));
+ $secpolicy =~ s/^\s+|\s+$//g;
+ $xframeop =~ s/^\s+|\s+$//g;
+ if (($secpolicy ne '') || ($xframeop ne '')) {
+ my ($remotehost) = ($url =~ m{^(https?\://[^/?#]+)});
+ $remotehost = lc($remotehost);
+ my ($origin,$protocol,$port);
+ if ($ENV{'SERVER_PORT'} =~/^\d+$/) {
+ $port = $ENV{'SERVER_PORT'};
+ } else {
+ $port = 80;
+ }
+ if ($absolute eq '') {
+ $protocol = 'http:';
+ if ($port == 443) {
+ $protocol = 'https:';
+ }
+ $origin = $protocol.'//'.lc($hostname);
+ } else {
+ $origin = lc($absolute);
+ ($protocol,$hostname) = ($absolute =~ m{^(https?:)//([^/]+)$});
+ }
+ if (($secpolicy) && ($secpolicy =~ /\Qframe-ancestors\E([^;]*)(;|$)/)) {
+ my $framepolicy = $1;
+ $framepolicy =~ s/^\s+|\s+$//g;
+ my @policies = split(/\s+/,$framepolicy);
+ if (@policies) {
+ if (grep(/^\Q'none'\E$/, at policies)) {
+ $uselink = 1;
+ } else {
+ $uselink = 1;
+ if ((grep(/^\Q*\E$/, at policies)) || (grep(/^\Q$protocol\E$/, at policies)) ||
+ (($origin ne '') && (grep(/^\Q$origin\E$/, at policies))) ||
+ (($ip ne '') && (grep(/^\Q$ip\E$/, at policies)))) {
+ undef($uselink);
+ }
+ if ($uselink) {
+ if (grep(/^\Q'self'\E$/, at policies)) {
+ if (($origin ne '') && ($remotehost eq $origin)) {
+ undef($uselink);
+ }
+ }
+ }
+ if ($uselink) {
+ my @possok;
+ if ($ip ne '') {
+ push(@possok,$ip);
+ }
+ my $hoststr = '';
+ foreach my $part (reverse(split(/\./,$hostname))) {
+ if ($hoststr eq '') {
+ $hoststr = $part;
+ } else {
+ $hoststr = "$part.$hoststr";
+ }
+ if ($hoststr eq $hostname) {
+ push(@possok,$hostname);
+ } else {
+ push(@possok,"*.$hoststr");
+ }
+ }
+ if (@possok) {
+ foreach my $poss (@possok) {
+ last if (!$uselink);
+ foreach my $policy (@policies) {
+ if ($policy =~ m{^(\Q$protocol\E//|)\Q$poss\E(\Q:$port\E|)$}) {
+ undef($uselink);
+ last;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } elsif ($xframeop ne '') {
+ $uselink = 1;
+ my @policies = split(/\s*,\s*/,$xframeop);
+ if (@policies) {
+ unless (grep(/^deny$/, at policies)) {
+ if ($origin ne '') {
+ if (grep(/^sameorigin$/, at policies)) {
+ if ($remotehost eq $origin) {
+ undef($uselink);
+ }
+ }
+ if ($uselink) {
+ foreach my $policy (@policies) {
+ if ($policy =~ /^allow-from\s*(.+)$/) {
+ my $allowfrom = $1;
+ if (($allowfrom ne '') && ($allowfrom eq $origin)) {
+ undef($uselink);
+ last;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return $uselink;
+}
+
+
1;
__END__;
Index: loncom/interface/lonsyllabus.pm
diff -u loncom/interface/lonsyllabus.pm:1.145 loncom/interface/lonsyllabus.pm:1.146
--- loncom/interface/lonsyllabus.pm:1.145 Thu Dec 27 20:10:31 2018
+++ loncom/interface/lonsyllabus.pm Thu May 2 02:12:19 2019
@@ -1,7 +1,7 @@
# The LearningOnline Network
# Syllabus
#
-# $Id: lonsyllabus.pm,v 1.145 2018/12/27 20:10:31 raeburn Exp $
+# $Id: lonsyllabus.pm,v 1.146 2019/05/02 02:12:19 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -177,7 +177,7 @@
$brcrum =
&Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1);
}
- $r->print(&Apache::lonwrapper::wrapper($item,$brcrum,$env{'request.use_absolute'},
+ $r->print(&Apache::lonwrapper::wrapper($r,$item,$brcrum,$env{'request.use_absolute'},
undef,$is_pdf,undef,&mt('Syllabus')));
}
}
@@ -204,7 +204,7 @@
$brcrum =
&Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1);
}
- $r->print(&Apache::lonwrapper::wrapper($external,$brcrum,$env{'request.use_absolute'},
+ $r->print(&Apache::lonwrapper::wrapper($r,$external,$brcrum,$env{'request.use_absolute'},
$is_ext,$is_pdf,undef,&mt('Syllabus')));
}
return OK;
Index: rat/lonwrapper.pm
diff -u rat/lonwrapper.pm:1.68 rat/lonwrapper.pm:1.69
--- rat/lonwrapper.pm:1.68 Sat Dec 30 00:16:36 2017
+++ rat/lonwrapper.pm Thu May 2 02:12:31 2019
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Wrapper for external and binary files as standalone resources
#
-# $Id: lonwrapper.pm,v 1.68 2017/12/30 00:16:36 raeburn Exp $
+# $Id: lonwrapper.pm,v 1.69 2019/05/02 02:12:31 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -44,7 +44,7 @@
# ================================================================ Main Handler
sub wrapper {
- my ($url,$brcrum,$absolute,$is_ext,$is_pdf,$exttool,$linktext,$explanation,
+ my ($r,$url,$brcrum,$absolute,$is_ext,$is_pdf,$exttool,$linktext,$explanation,
$title,$width,$height) = @_;
my $forcereg;
@@ -56,7 +56,7 @@
'show' => 'Show content in pop-up window',
);
- my $anchor;
+ my ($anchor,$uselink);
if ($is_ext) {
if ($env{'form.symb'}) {
(undef,undef,my $res) = &Apache::lonnet::decode_symb($env{'form.symb'});
@@ -66,6 +66,12 @@
} elsif ($env{'form.anchor'} ne '') {
$anchor = '#'.$env{'form.anchor'};
}
+ unless (($is_pdf) && ($env{'browser.mobile'})) {
+ my $hostname = $r->hostname();
+ my $lonhost = $r->dir_config('lonHostID');
+ my $ip = &Apache::lonnet::get_host_ip($lonhost);
+ $uselink = &Apache::loncommon::is_nonframeable($url,$absolute,$hostname,$ip);
+ }
}
my $noiframe = &Apache::loncommon::modal_link($url.$anchor,$lt{'show'},500,400);
@@ -132,22 +138,21 @@
my $startpage = &Apache::loncommon::start_page('Menu',undef,$args).$countdown.$donemsg;
my $endpage = &Apache::loncommon::end_page();
+ if (($uselink) && ($title eq '')) {
+ if ($env{'form.symb'}) {
+ $title=&Apache::lonnet::gettitle($env{'form.symb'});
+ } else {
+ my $symb=&Apache::lonnet::symbread($r->uri);
+ if ($symb) {
+ $title=&Apache::lonnet::gettitle($symb);
+ }
+ }
+ }
if (($env{'browser.mobile'}) || ($exttool eq 'window') || ($exttool eq 'tab')) {
my $output = $startpage;
if ($is_pdf) {
- if ($title eq '') {
- $title = $env{'form.title'};
- if ($title eq '') {
- unless ($env{'request.enc'}) {
- ($title) = ($url =~ m{/([^/]+)$});
- $title =~ s/(\?[^\?]+)$//;
- }
- }
- }
- unless ($title eq '') {
- $output .= $title.'<br />';
- }
- $output .= '<a href="'.$url.'">'.&mt('Link to PDF (for mobile devices)').'</a>';
+ $linktext = &mt('Link to PDF (for mobile devices)');
+ $output .= &create_link($url,$anchor,$title,$linktext);
} elsif (($exttool eq 'window') || ($exttool eq 'tab')) {
if ($linktext eq '') {
$linktext = &mt('Launch External Tool');
@@ -193,15 +198,23 @@
$output .= &Apache::lonfeedback::list_discussion('tool','OPEN');
}
} else {
- my $dest = &HTML::Entities::encode($url.$anchor,'&<>"');
- $output .= '<div style="overflow:scroll; -webkit-overflow-scrolling:touch;">'."\n".
- '<iframe src="'.$dest.'" height="100%" width="100%" frameborder="0">'."\n".
- "$lt{'noif'} $noiframe\n".
- "</iframe>\n".
- "</div>\n";
+ if ($uselink) {
+ $linktext = &mt('Link to resource');
+ $output .= &create_link($url,$anchor,$title,$linktext);
+ } else {
+ my $dest = &HTML::Entities::encode($url.$anchor,'&<>"');
+ $output .= '<div style="overflow:scroll; -webkit-overflow-scrolling:touch;">'."\n".
+ '<iframe src="'.$dest.'" height="100%" width="100%" frameborder="0">'."\n".
+ "$lt{'noif'} $noiframe\n".
+ "</iframe>\n".
+ "</div>\n";
+ }
}
$output .= $endpage;
return $output;
+ } elsif ($uselink) {
+ $linktext = &mt('Link to resource');
+ return $startpage.&create_link($url,$anchor,$title,$linktext).$endpage;
} else {
my $offset = 5;
&Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['inhibitmenu']);
@@ -246,6 +259,26 @@
}
}
+sub create_link {
+ my ($url,$anchor,$title,$linktext) = @_;
+ my $shownlink;
+ if ($title eq '') {
+ $title = $env{'form.title'};
+ if ($title eq '') {
+ unless ($env{'request.enc'}) {
+ ($title) = ($url =~ m{/([^/]+)$});
+ $title =~ s/(\?[^\?]+)$//;
+ }
+ }
+ }
+ unless ($title eq '') {
+ $shownlink = '<span style="font-weight:bold;">'.$title.'</span><br />';
+ }
+ my $dest = &HTML::Entities::encode($url.$anchor,'&<>"');
+ $shownlink .= '<a href="'.$dest.'">'.$linktext.'</a>';
+ return $shownlink;
+}
+
sub handler {
my $r=shift;
&Apache::loncommon::content_type($r,'text/html');
@@ -264,7 +297,6 @@
s|:|:|g;
}
-
if ($url =~ /\.pdf$/i) {
$is_pdf = 1;
} elsif ($url =~ m{^/adm/($match_domain)/($match_courseid)/(\d+)/ext\.tool$}) {
@@ -367,7 +399,7 @@
&Apache::lonenc::check_encrypt(\$url);
}
- $r->print( wrapper($url,$brcrum,$absolute,$is_ext,$is_pdf,$exttool,
+ $r->print( wrapper($r,$url,$brcrum,$absolute,$is_ext,$is_pdf,$exttool,
$linktext,$explanation,undef,$width,$height) );
} # not just the menu
@@ -395,10 +427,14 @@
=over
-=item wrapper($url,$brcrum,$absolute,$is_ext,$is_pdf,$linktext,$explanation,$title,$width,$height)
+=item wrapper($r,$url,$brcrum,$absolute,$is_ext,$is_pdf,$linktext,$explanation,$title,$width,$height)
=over
+=item $r
+
+request object
+
=item $url
url to display by including in an iframe within a
Index: doc/loncapafiles/loncapafiles.lpml
diff -u doc/loncapafiles/loncapafiles.lpml:1.984 doc/loncapafiles/loncapafiles.lpml:1.985
--- doc/loncapafiles/loncapafiles.lpml:1.984 Tue Apr 9 15:54:12 2019
+++ doc/loncapafiles/loncapafiles.lpml Thu May 2 02:12:42 2019
@@ -2,7 +2,7 @@
"http://lpml.sourceforge.net/DTD/lpml.dtd">
<!-- loncapafiles.lpml -->
-<!-- $Id: loncapafiles.lpml,v 1.984 2019/04/09 15:54:12 raeburn Exp $ -->
+<!-- $Id: loncapafiles.lpml,v 1.985 2019/05/02 02:12:42 raeburn Exp $ -->
<!--
@@ -2590,6 +2590,15 @@
<status>works/unverified</status>
</file>
<file>
+<source>loncom/interface/lonexturlcheck.pm </source>
+<target dist='default'>home/httpd/lib/perl/Apache/lonexturlcheck.pm</target>
+<categoryname>handler</categoryname>
+<description>
+Handler to check if an external resource can be displayed in an iframe
+</description>
+<status>works/unverified</status>
+</file>
+<file>
<source>loncom/interface/lonexttool.pm</source>
<target dist='default'>home/httpd/lib/perl/Apache/lonexttool.pm</target>
<categoryname>handler</categoryname>
Index: loncom/interface/lonexturlcheck.pm
+++ loncom/interface/lonexturlcheck.pm
# The LearningOnline Network with CAPA
# Handler to check if external resource can be shown in iframe
#
# $Id: lonexturlcheck.pm,v 1.1 2019/05/02 02:12:19 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#
#
###############################################################
###############################################################
=pod
=head1 NAME
Apache::lonexturlcheck - External Resource URL checker
=head1 SYNOPSIS
Called in course context by course personnel either with the course editing
privilege or with view-only access to course editing tools.
Query string contains one item: name=exturl, value=URL of external resource
(format: http://hostname/path or https://hostname/path).
The resource URL is sent to &loncommon::is_nonframeable() to check whether
it can be displayed in an iframe in a page served by the current host.
=head1 OVERVIEW
Input: external resource URL (from query string passed to /adm/exturlcheck).
Hostname, lonHostID, and IP address for this node are retrieved from Apache.
Dependencies: calls &loncommon::is_nonframeable() to check if server where
external resource is hosted is configured with a Content-Security-Policy or
with X-Frame-options settings which prohibit display of the resource within
an iframe in a LON-CAPA page served from this node.
Output to print buffer: (content-type: text/plain): 1, 0, -1 or empty string.
'' -- display in iframe is allowed
1 -- display in iframe not allowed
0 -- invalid URL
-1 -- could not verify course editing privilege or view-only access to
course editing tools
HTTP Return codes:
406 -- if user is not in a course
200 -- otherwise
=cut
package Apache::lonexturlcheck;
use strict;
use Apache::Constants qw(:common :http);
use Apache::lonnet;
use Apache::loncommon;
use LONCAPA::LWPReq;
use HTTP::Request;
sub handler {
my $r=shift;
if ($r->header_only) {
&Apache::loncommon::content_type($r,'text/html');
$r->send_http_header;
return OK;
}
if (!$env{'request.course.fn'}) {
# Not in a course.
$env{'user.error.msg'}="/adm/lonexturlcheck:bre:0:0:Not in a course";
return HTTP_NOT_ACCEPTABLE;
}
&Apache::loncommon::content_type($r,'text/plain');
$r->send_http_header;
my $uselink;
if (($env{'request.course.id'}) &&
((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) ||
(&Apache::lonnet::allowed('cev',$env{'request.course.id'})))) {
&Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['exturl']);
if ($env{'form.exturl'} =~ m{^https?\://[^/]+}) {
my $hostname = $r->hostname();
my $lonhost = $r->dir_config('lonHostID');
my $ip = &Apache::lonnet::get_host_ip($lonhost);
$r->print(&Apache::loncommon::is_nonframeable($env{'form.exturl'},'',$hostname,$ip));
} else {
$r->print(0);
}
} else {
$r->print(-1);
}
return OK;
}
1;
More information about the LON-CAPA-cvs
mailing list