Browse Source

Check profile update content and improve error messages. Fix #159, #903.

FelisCatus 9 years ago
parent
commit
fcb063a464

+ 16 - 0
omega-locales/ach/LC_MESSAGES/omega-web.po

@@ -958,6 +958,22 @@ msgstr "Successfully updated profile."
 msgid "options_profileDownloadError"
 msgstr "Error downloading profile data!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "Download Profile Now"
 

+ 16 - 0
omega-locales/cs/LC_MESSAGES/omega-web.po

@@ -952,6 +952,22 @@ msgstr "Profil byl úspěšně aktualizován."
 msgid "options_profileDownloadError"
 msgstr "Chyba při stahování dat profilu!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "Stáhnout profil"
 

+ 16 - 0
omega-locales/de/LC_MESSAGES/omega-web.po

@@ -963,6 +963,22 @@ msgstr "Successfully updated profile."
 msgid "options_profileDownloadError"
 msgstr "Error downloading profile data!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "Download Profile Now"
 

+ 16 - 0
omega-locales/en_US/LC_MESSAGES/omega-web.po

@@ -948,6 +948,22 @@ msgstr "Successfully updated profile."
 msgid "options_profileDownloadError"
 msgstr "Error downloading profile data!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "Download Profile Now"
 

+ 16 - 0
omega-locales/es_AR/LC_MESSAGES/omega-web.po

@@ -963,6 +963,22 @@ msgstr "Successfully updated profile."
 msgid "options_profileDownloadError"
 msgstr "Error downloading profile data!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "Download Profile Now"
 

+ 16 - 0
omega-locales/fa/LC_MESSAGES/omega-web.po

@@ -962,6 +962,22 @@ msgstr "پروفایل با موفقیت بروزسانی شد ."
 msgid "options_profileDownloadError"
 msgstr "خطا در حین دانلود داده ی پروفایل !"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "هم اکنون پروفایل را دانلود کنید"
 

+ 16 - 0
omega-locales/fr/LC_MESSAGES/omega-web.po

@@ -960,6 +960,22 @@ msgstr "Successfully updated profile."
 msgid "options_profileDownloadError"
 msgstr "Error downloading profile data!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "Download Profile Now"
 

+ 16 - 0
omega-locales/is/LC_MESSAGES/omega-web.po

@@ -817,6 +817,22 @@ msgstr ""
 msgid "options_profileDownloadError"
 msgstr ""
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr ""
 

+ 16 - 0
omega-locales/ja/LC_MESSAGES/omega-web.po

@@ -954,6 +954,22 @@ msgstr "Successfully updated profile."
 msgid "options_profileDownloadError"
 msgstr "Error downloading profile data!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "Download Profile Now"
 

+ 16 - 0
omega-locales/nb_NO/LC_MESSAGES/omega-web.po

@@ -840,6 +840,22 @@ msgstr "Vellykket oppdatering av profil."
 msgid "options_profileDownloadError"
 msgstr "Feil ved nedlasting av profildata!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "Last ned profil nå"
 

+ 16 - 0
omega-locales/pt_BR/LC_MESSAGES/omega-web.po

@@ -959,6 +959,22 @@ msgstr "Successfully updated profile."
 msgid "options_profileDownloadError"
 msgstr "Error downloading profile data!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "Download Profile Now"
 

+ 16 - 0
omega-locales/ru/LC_MESSAGES/omega-web.po

@@ -963,6 +963,22 @@ msgstr "Successfully updated profile."
 msgid "options_profileDownloadError"
 msgstr "Error downloading profile data!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "Download Profile Now"
 

+ 16 - 0
omega-locales/sk/LC_MESSAGES/omega-web.po

@@ -816,6 +816,22 @@ msgstr ""
 msgid "options_profileDownloadError"
 msgstr ""
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr ""
 

+ 16 - 0
omega-locales/sl/LC_MESSAGES/omega-web.po

@@ -818,6 +818,22 @@ msgstr ""
 msgid "options_profileDownloadError"
 msgstr ""
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr ""
 

+ 16 - 0
omega-locales/tr/LC_MESSAGES/omega-web.po

@@ -960,6 +960,22 @@ msgstr "Successfully updated profile."
 msgid "options_profileDownloadError"
 msgstr "Error downloading profile data!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "A network error occurred when updating."
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "An HTTP error ($STATUS$) occurred when updating."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "The Profile URL was not found on the server. Please double-check."
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "The remote server responded with error ($STATUS$) when updating."
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "The downloaded data is invalid! "
+"You may open the Profile URL in your browser to inspect it."
+
 msgid "options_downloadProfileNow"
 msgstr "Download Profile Now"
 

+ 15 - 0
omega-locales/zh_CN/LC_MESSAGES/omega-web.po

@@ -852,6 +852,21 @@ msgstr "情景模式已经更新成功。"
 msgid "options_profileDownloadError"
 msgstr "下载情景模式数据时出错!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "更新时发生网络错误。"
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "更新时发生网络错误 (HTTP $STATUS$)."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "在远程服务器上找不到情景模式网址对应的文件。请检查网址。"
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "更新时远程服务器发生错误 ($STATUS$)。"
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "下载的数据不符合格式!建议您在浏览器中打开情景模式网址并检查其内容。"
+
 msgid "options_downloadProfileNow"
 msgstr "立即更新情景模式"
 

+ 15 - 0
omega-locales/zh_TW/LC_MESSAGES/omega-web.po

@@ -852,6 +852,21 @@ msgstr "情景模式已經更新成功。"
 msgid "options_profileDownloadError"
 msgstr "下載情景模式資料時出錯!"
 
+msgid "options_profileDownloadError_NetworkError"
+msgstr "更新時發生網絡錯誤。"
+
+msgid "options_profileDownloadError_HttpError"
+msgstr "更新時發生網絡錯誤 (HTTP $STATUS$)."
+
+msgid "options_profileDownloadError_HttpNotFoundError"
+msgstr "在遠程服務器上找不到情景模式網址對應的文件。請檢查網址。"
+
+msgid "options_profileDownloadError_HttpServerError"
+msgstr "更新時遠程服務器發生錯誤 ($STATUS$)。"
+
+msgid "options_profileDownloadError_ContentTypeRejectedError"
+msgstr "下載的數據不符合格式!建議您在瀏覽器中打開情景模式網址並檢查其內容。"
+
 msgid "options_downloadProfileNow"
 msgstr "立即更新情景模式"
 

+ 14 - 0
omega-pac/src/profiles.coffee

@@ -108,6 +108,8 @@ module.exports = exports =
 
   updateUrl: (profile) ->
     exports._handler(profile).updateUrl?.call(exports, profile)
+  updateContentTypeHints: (profile) ->
+    exports._handler(profile).updateContentTypeHints?.call(exports, profile)
   update: (profile, data) ->
     exports._handler(profile).update.call(exports, profile, data)
 
@@ -344,6 +346,12 @@ module.exports = exports =
           undefined
         else
           profile.pacUrl
+      updateContentTypeHints: -> [
+        '!text/html'
+        '!application/xhtml+xml'
+        'application/x-ns-proxy-autoconfig'
+        'application/x-javascript-config'
+      ]
       update: (profile, data) ->
         return false if profile.pacScript == data
         profile.pacScript = data
@@ -442,6 +450,12 @@ module.exports = exports =
       compile: (profile) ->
         exports.compile(profile, 'SwitchProfile')
       updateUrl: (profile) -> profile.sourceUrl
+      updateContentTypeHints: -> [
+        '!text/html'
+        '!application/xhtml+xml'
+        'text/plain'
+        '*'
+      ]
       update: (profile, data) ->
         data = data.trim()
         original = profile.format ? exports.formatByType[profile.profileType]

+ 80 - 0
omega-target-chromium-extension/src/fetch_url.coffee

@@ -0,0 +1,80 @@
+Promise = OmegaTarget.Promise
+xhr = Promise.promisify(require('xhr'))
+Url = require('url')
+ContentTypeRejectedError = OmegaTarget.ContentTypeRejectedError
+
+xhrWrapper = (args...) ->
+  xhr(args...).catch (err) ->
+    throw err unless err.isOperational
+    if not err.statusCode?
+      throw new OmegaTarget.NetworkError(err)
+    if err.statusCode == 404
+      throw new OmegaTarget.HttpNotFoundError(err)
+    if err.statusCode >= 500 and err.statusCode < 600
+      throw new OmegaTarget.HttpServerError(err)
+    throw new OmegaTarget.HttpError(err)
+
+fetchUrl = (dest_url, opt_bypass_cache, opt_type_hints) ->
+  getResBody = ([response, body]) ->
+    return body unless opt_type_hints
+    contentType = response.headers['content-type']?.toLowerCase()
+    for hint in opt_type_hints
+      handler = hintHandlers[hint] ? defaultHintHandler
+      result = handler(response, body, {contentType, hint})
+      return result if result?
+    throw new ContentTypeRejectedError(
+      'Unrecognized Content-Type: ' + contentType)
+    return body
+
+  if opt_bypass_cache
+    parsed = Url.parse(dest_url, true)
+    parsed.search = undefined
+    parsed.query['_'] = Date.now()
+    dest_url_nocache = Url.format(parsed)
+    # Try first with the dumb parameter to bypass cache.
+    xhrWrapper(dest_url_nocache).then(getResBody).catch ->
+      # If failed, try again with the original URL.
+      xhrWrapper(dest_url).then(getResBody)
+  else
+    xhrWrapper(dest_url).then(getResBody)
+
+defaultHintHandler = (response, body, {contentType, hint}) ->
+  if '!' + contentType == hint
+    throw new ContentTypeRejectedError(
+      'Response Content-Type blacklisted: ' + contentType)
+  if contentType == hint
+    return body
+
+hintHandlers =
+  '*': (response, body) ->
+    # Allow all contents.
+    return body
+
+  '!text/html': (response, body, {contentType, hint}) ->
+    if contentType == hint
+      # Sometimes other content can also be served with the text/html
+      # Content-Type header. So we check if the body actually looks like HTML.
+      looksLikeHtml = false
+      if body.indexOf('<!DOCTYPE') >= 0 || body.indexOf('<!doctype') >= 0
+        looksLikeHtml = true
+      else if body.indexOf('</html>') >= 0
+        looksLikeHtml = true
+      else if body.indexOf('</body>') >= 0
+        looksLikeHtml = true
+
+      if looksLikeHtml
+        throw new ContentTypeRejectedError('Response must not be HTML.')
+
+  '!application/xhtml+xml': (args...) -> hintHandlers['!text/html'](args...)
+
+  'application/x-ns-proxy-autoconfig': (response, body, {contentType, hint}) ->
+    if contentType == hint
+      return body
+    # Sometimes PAC scripts can also be served using with wrong Content-Type.
+    if body.indexOf('FindProxyForURL') >= 0
+      return body
+    else
+      # The content is not a PAC script if it does not contain FindProxyForURL.
+      return undefined
+
+module.exports = fetchUrl

+ 2 - 9
omega-target-chromium-extension/src/options.coffee

@@ -1,8 +1,6 @@
 OcontextMenu_inspectElementmegaTarget = require('omega-target')
 OmegaPac = OmegaTarget.OmegaPac
 Promise = OmegaTarget.Promise
-xhr = Promise.promisify(require('xhr'))
-Url = require('url')
 querystring = require('querystring')
 chromeApiPromisifyAll = require('./chrome_api')
 proxySettings = chromeApiPromisifyAll(chrome.proxy.settings)
@@ -10,19 +8,14 @@ parseExternalProfile = require('./parse_external_profile')
 ProxyAuth = require('./proxy_auth')
 WebRequestMonitor = require('./web_request_monitor')
 ChromePort = require('./chrome_port')
+fetchUrl = require('./fetch_url')
 
 class ChromeOptions extends OmegaTarget.Options
   _inspect: null
   parseExternalProfile: (details) ->
     parseExternalProfile(details, @_options, @_fixedProfileConfig.bind(this))
 
-  fetchUrl: (dest_url, opt_bypass_cache) ->
-    if opt_bypass_cache
-      parsed = Url.parse(dest_url, true)
-      parsed.search = undefined
-      parsed.query['_'] = Date.now()
-      dest_url = Url.format(parsed)
-    xhr(dest_url).get(1)
+  fetchUrl: fetchUrl
 
   updateProfile: (args...) ->
     super(args...).then (results) ->

+ 3 - 0
omega-target/index.coffee

@@ -8,3 +8,6 @@ module.exports =
 
 for name, value of require('./src/utils.coffee')
   module.exports[name] = value
+
+for name, value of require('./src/errors.coffee')
+  module.exports[name] = value

+ 33 - 0
omega-target/src/errors.coffee

@@ -0,0 +1,33 @@
+class NetworkError extends Error
+  constructor: (err) ->
+    super
+    this.cause = err
+    this.name = 'NetworkError'
+
+class HttpError extends NetworkError
+  constructor: ->
+    super
+    this.statusCode = this.cause?.statusCode
+    this.name = 'HttpError'
+
+class HttpNotFoundError extends HttpError
+  constructor: ->
+    super
+    this.name = 'HttpNotFoundError'
+
+class HttpServerError extends HttpError
+  constructor: ->
+    super
+    this.name = 'HttpServerError'
+
+class ContentTypeRejectedError extends Error
+  constructor: ->
+    super
+    this.name = 'ContentTypeRejectedError'
+
+module.exports =
+  NetworkError: NetworkError
+  HttpError: HttpError
+  HttpNotFoundError: HttpNotFoundError
+  HttpServerError: HttpServerError
+  ContentTypeRejectedError: ContentTypeRejectedError

+ 5 - 2
omega-target/src/options.coffee

@@ -642,7 +642,9 @@ class Options
           return unless profile.name == name
       url = OmegaPac.Profiles.updateUrl(profile)
       if url
-        results[key] = @fetchUrl(url, opt_bypass_cache).then((data) =>
+        type_hints = OmegaPac.Profiles.updateContentTypeHints(profile)
+        fetchResult = @fetchUrl(url, opt_bypass_cache, type_hints)
+        results[key] = fetchResult.then((data) =>
           # Errors and unsuccessful response codes shoud have been already
           # rejected by fetchUrl and will not end up here.
           # So empty data indicates success without any update (e.g. 304).
@@ -666,9 +668,10 @@ class Options
   # In base class, this method is not implemented and will always reject.
   # @param {string} url The name of the profiles,
   # @param {?bool} opt_bypass_cache Do not read from the cache if true
+  # @param {?string} opt_type_hints MIME type hints for downloaded content.
   # @returns {Promise<String>} The text content fetched from the url
   ###
-  fetchUrl: (url, opt_bypass_cache) ->
+  fetchUrl: (url, opt_bypass_cache, opt_type_hints) ->
     Promise.reject new Error('not implemented')
 
   _replaceRefChanges: (fromName, toName, changes) ->

+ 17 - 5
omega-web/src/omega/controllers/master.coffee

@@ -237,12 +237,24 @@ angular.module('omega').controller 'MasterCtrl', ($scope, $rootScope, $window,
             i18n: 'options_profileDownloadSuccess'
           )
         else
-          $q.reject(results)
+          if error == 1
+            singleErr = results[OmegaPac.Profiles.nameAsKey(name)]
+            if singleErr
+              return $q.reject(singleErr)
+          return $q.reject(results)
       ).catch((err) ->
-        $rootScope.showAlert(
-          type: 'error'
-          i18n: 'options_profileDownloadError'
-        )
+        message = tr('options_profileDownloadError_' + err.name,
+          [err.statusCode ? err.original?.statusCode ? ''])
+        if message
+          $rootScope.showAlert(
+            type: 'error'
+            message: message
+          )
+        else
+          $rootScope.showAlert(
+            type: 'error'
+            i18n: 'options_profileDownloadError'
+          )
       ).finally ->
         if name?
           $scope.updatingProfile[name] = false