/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.modules.tu; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.utils.URIBuilder; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.olat.basesecurity.BaseSecurityModule; import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.AbstractComponent; import org.olat.core.gui.components.ComponentRenderer; import org.olat.core.gui.media.AsyncMediaResponsible; import org.olat.core.gui.media.HttpRequestMediaResource; import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.render.ValidationResult; import org.olat.core.id.User; import org.olat.core.id.UserConstants; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.SimpleHtmlParser; import org.olat.core.util.StringHelper; import org.olat.course.nodes.tu.TUConfigForm; import org.olat.course.nodes.tu.TURequest; import org.olat.modules.ModuleConfiguration; /** * @author Mike Stock Comment: */ public class TunnelComponent extends AbstractComponent implements AsyncMediaResponsible { private static final OLog log = Tracing.createLoggerFor(TunnelComponent.class); private static final ComponentRenderer RENDERER = new TunnelRenderer(); private String proto; private String host; private Integer port; private String query; private HttpClient httpClientInstance; private ModuleConfiguration config; private String htmlHead; private String jsOnLoad; private String htmlContent; private boolean firstCall = true; private String startUri; private String ref; /** * @param name * @param config * @param ureq */ public TunnelComponent(String name, ModuleConfiguration config, HttpClient httpClientInstance, UserRequest ureq) { super(name); this.config = config; this.httpClientInstance = httpClientInstance; int configVersion = config.getConfigurationVersion(); //since config version 1 proto = (String) config.get(TUConfigForm.CONFIGKEY_PROTO); host = (String) config.get(TUConfigForm.CONFIGKEY_HOST); port = (Integer) config.get(TUConfigForm.CONFIGKEY_PORT); startUri = (String) config.get(TUConfigForm.CONFIGKEY_URI); ref = (String)config.get(TUConfigForm.CONFIGKEY_REF); if(configVersion==2) { //query string is available since config version 2 query = (String) config.get(TUConfigForm.CONFIGKEY_QUERY); } fetchFirstResource(ureq); } /** * @return String */ public String getType() { return "hw"; } private void fetchFirstResource(UserRequest ureq) { TURequest tureq = new TURequest(); //config, ureq); tureq.setContentType(null); // not used tureq.setMethod("GET"); tureq.setParameterMap(Collections.<String,String[]>emptyMap()); tureq.setQueryString(query); if(startUri != null){ if(startUri.startsWith("/")){ tureq.setUri(startUri); }else{ tureq.setUri("/"+startUri); } } fillTURequestWithUserInfo(tureq,ureq); HttpResponse response = fetch(tureq, httpClientInstance); if (response == null) { setFetchError(); }else{ Header responseHeader = response.getFirstHeader("Content-Type"); String mimeType; if (responseHeader == null) { setFetchError(); mimeType = null; } else { mimeType = responseHeader.getValue(); } if (mimeType != null && mimeType.startsWith("text/html")) { // we have html content, let doDispatch handle it for // inline rendering, update hreq for next content request String body; try { body = EntityUtils.toString(response.getEntity()); } catch (IOException e) { log.warn("Problems when tunneling URL::" + tureq.getUri(), e); htmlContent = "Error: cannot display inline :"+tureq.getUri()+": Unknown transfer problem '"; return; } SimpleHtmlParser parser = new SimpleHtmlParser(body); if (!parser.isValidHtml()) { // this is not valid HTML, deliver // asynchronuous } htmlHead = parser.getHtmlHead(); jsOnLoad = parser.getJsOnLoad(); htmlContent = parser.getHtmlContent(); } else { htmlContent = "Error: cannot display inline :"+tureq.getUri()+": mime type was '" + mimeType + "' but expected 'text/html'. Response header was '" + responseHeader + "'."; } } } /** * fills the given TURequest with userInformation such as lastName, * firstName, email, ipAddress * * @param tuRequest * @param userRequest */ private void fillTURequestWithUserInfo(TURequest tuRequest, UserRequest userRequest){ if("enabled".equals(CoreSpringFactory.getImpl(BaseSecurityModule.class).getUserInfosTunnelCourseBuildingBlock())) { String userName = userRequest.getIdentity().getName(); User u = userRequest.getIdentity().getUser(); String lastName = u.getProperty(UserConstants.LASTNAME, null); String firstName = u.getProperty(UserConstants.FIRSTNAME, null); String email = u.getProperty(UserConstants.EMAIL, null); String userIPAdress = userRequest.getUserSession().getSessionInfo().getFromIP(); tuRequest.setEmail(email); tuRequest.setFirstName(firstName); tuRequest.setLastName(lastName); tuRequest.setUserName(userName); tuRequest.setUserIPAddress(userIPAdress); } } /** * @see org.olat.core.gui.media.AsyncMediaResponsible#getAsyncMediaResource(org.olat.core.gui.UserRequest) */ public MediaResource getAsyncMediaResource(UserRequest ureq) { String moduleURI = ureq.getModuleURI(); //FIXME:fj: can we distinguish between a ../ call an a click to another component? // now works for start uri's like /demo/tunneldemo.php but when in tunneldemo.php // a link is used like ../ this link does not work (moduleURI is null). if i use // ../index.php instead everything works as expected if (moduleURI == null) { // after a click on some other component e.g. if (!firstCall) return null; firstCall = false; // reset first call } TURequest tureq = new TURequest(config, ureq); fillTURequestWithUserInfo(tureq,ureq); HttpResponse response = fetch(tureq, httpClientInstance); if (response == null) { setFetchError(); return null; } Header responseHeader = response.getFirstHeader("Content-Type"); if (responseHeader == null) { setFetchError(); return null; } String mimeType = responseHeader.getValue(); if (mimeType != null && mimeType.startsWith("text/html")) { // we have html content, let doDispatch handle it for // inline rendering, update hreq for next content request String body; try { body = EntityUtils.toString(response.getEntity()); } catch (IOException e) { log.warn("Problems when tunneling URL::" + tureq.getUri(), e); return null; } SimpleHtmlParser parser = new SimpleHtmlParser(body); if (!parser.isValidHtml()) { // this is not valid HTML, deliver // asynchronuous return new HttpRequestMediaResource(response); } htmlHead = parser.getHtmlHead(); jsOnLoad = parser.getJsOnLoad(); htmlContent = parser.getHtmlContent(); setDirty(true); } else { return new HttpRequestMediaResource(response); // this is a async browser } // refetch return null; } private void setFetchError() { // some fetch error - reset to generic error message htmlHead = null; jsOnLoad = null; htmlContent = "Server error: No connection to tunneling server. Please check your configuration!"; } /** * @param tuReq * @param client * @return HttpMethod */ public HttpResponse fetch(TURequest tuReq, HttpClient client) { try { String modulePath = tuReq.getUri(); URIBuilder builder = new URIBuilder(); builder.setScheme(proto).setHost(host).setPort(port.intValue()); if (modulePath == null) { modulePath = (startUri == null) ? "" : startUri; } if (modulePath.length() > 0 && modulePath.charAt(0) != '/') { modulePath = "/" + modulePath; } if(StringHelper.containsNonWhitespace(modulePath)) { builder.setPath(modulePath); } HttpUriRequest meth = null; String method = tuReq.getMethod(); if (method.equals("GET")) { String queryString = tuReq.getQueryString(); if (queryString != null) { builder.setCustomQuery(queryString); } meth = new HttpGet(builder.build()); } else if (method.equals("POST")) { Map<String,String[]> params = tuReq.getParameterMap(); HttpPost pmeth = new HttpPost(builder.build()); List<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>(); for (String key: params.keySet()) { String vals[] = params.get(key); for(String val:vals) { pairs.add(new BasicNameValuePair(key, val)); } } HttpEntity entity = new UrlEncodedFormEntity(pairs, "UTF-8"); pmeth.setEntity(entity); meth = pmeth; } // Add olat specific headers to the request, can be used by external // applications to identify user and to get other params // test page e.g. http://cgi.algonet.se/htbin/cgiwrap/ug/test.py if("enabled".equals(CoreSpringFactory.getImpl(BaseSecurityModule.class).getUserInfosTunnelCourseBuildingBlock())) { meth.addHeader("X-OLAT-USERNAME", tuReq.getUserName()); meth.addHeader("X-OLAT-LASTNAME", tuReq.getLastName()); meth.addHeader("X-OLAT-FIRSTNAME", tuReq.getFirstName()); meth.addHeader("X-OLAT-EMAIL", tuReq.getEmail()); meth.addHeader("X-OLAT-USERIP", tuReq.getUserIPAddress()); } return client.execute(meth); } catch (Exception e) { log.error("", e); return null; } } /** * @see org.olat.core.gui.components.Component#dispatchRequest(org.olat.core.gui.UserRequest) */ protected void doDispatchRequest(UserRequest ureq) { // never called } /** * @see org.olat.core.gui.components.Component#getExtendedDebugInfo() */ public String getExtendedDebugInfo() { return proto + ", " + host + ", " + port; } /** * @return Returns the htmlContent. */ public String getHtmlContent() { return htmlContent; } /** * @return Returns the htmlHead. */ public String getHtmlHead() { return htmlHead; } /** * @return Returns the jsOnLoad. */ public String getJsOnLoad() { return jsOnLoad; } /** * @see org.olat.core.gui.components.Component#validate(org.olat.core.gui.UserRequest, * org.olat.core.gui.render.ValidationResult) */ public void validate(UserRequest ureq, ValidationResult vr) { super.validate(ureq, vr); // browser uri: e.g null or String browserURI = ureq.getModuleURI(); boolean redirect = true; if (browserURI == null) { // click on a treenode -> return without redirect // only if the currentURI is null (blank content) // or it is a root file if (startUri == null || startUri.indexOf("/") == -1) { redirect = false; } } else if (!ureq.isValidDispatchURI()) { // link from external // direct-jump-url or such -> // redirect redirect = true; } else { // browser uri != null and normal framework url dispatch = click from // within a page; currentURI == browserURI since asyncmedia-call took // place before validating. // never needs to redirect since browser page calculates relative page and // is handled by asyncmediaresponsible //FIXME:fj:a let userrequest tell me when is if (browserURI.startsWith("olatcmd")) { redirect = true; } else { redirect = false; } } if (redirect) { String newUri = startUri; if (newUri.charAt(0) == '/') { newUri = newUri.substring(1); } vr.setNewModuleURI(newUri); } return; } public ComponentRenderer getHTMLRendererSingleton() { return RENDERER; } }